1. Introducción

Los grÔficos e infografías son, cada vez mÔs, una parte importante de la mayoría de textos escritos, ya sean estos un informe técnico, un artículo de periódico o un TFG. En el caso de la ciencia de datos, como puede verse abajo en la infografía, el anÔlisis y la exploración de los datos es un proceso iterativo en el que la visualización y la generación de grÔficos ocupa un lugar destacado.

R for Data Science (http://r4ds.had.co.nz/)

R for Data Science (http://r4ds.had.co.nz/)

Uno de los instrumentos y tareas fundamentales de un científico de datos es la capacidad de realizar visualizaciones de datos apropiadas y convincentes. El anÔlisis grÔfico no solo ayuda en la exploración y comprensión de los datos, sino que es fundamental a la hora de mostrar las posibles relaciones entre variables, descubrir relaciones o patrones ocultos, y descartar o sugerir nuevas preguntas sobre los datos.

El entorno R tiene diversos sistemas para visualizar datos, los dos mÔs utilizados son el sistema grÔfico de R-base y el ecosistema asociado al paquete ggplot2. Por diversas razones, en el curso usaremos el entorno ggplot2 para hacer nuestros grÔficos; de hecho, en la actualidad ggplot2, dada su rapidez en la iteración entre grÔficos, versatilidad y la cuidada estética que tienen sus grÔficos, se ha convertido, al menos por el momento, en el sistema estÔndar para hacer grÔficos en R. Por ejemplo, ¿cómo creéis que hace la BBC sus grÔficos?, evidentemente con R y ggplot2. Puedes ver uno de sus repositorios aquí y su cookbook aquí.

Con ggplot2 es sencillo hacer grƔficos con calidad para ser publicados o mostrados, ademƔs de que, dada su sintaxis modular, hace sencillo el reutilizar los grƔficos durante el proceso de anƔlisis. El paquete ggplot2 fue inicialmente desarrollado por Hadley Wickham, pero actualmente el ecosistema ggplot es el resultado de toda una comunidad de usuarios que contribuye a enriquecer el sistema grƔfico con sus extensiones y paquetes auxiliares.

En palabras de Hadley en su libro sobre ggplot2:

ggplot2 is an R package for producing statistical, or data, graphics, but it is unlike most other graphics packages because it has a deep underlying grammar. This grammar, based on the Grammar of Graphics (Wilkinson 2005), is made up of a set of independent components that can be composed in many different ways. This makes ggplot2 very powerful because you are not limited to a set of pre-specified graphics, but you can create new graphics that are precisely tailored for your problem. This may sound overwhelming, but because there is a simple set of core principles and very few special cases, ggplot2 is also easy to learn (although it may take a little time to forget your preconceptions from other graphics tools).

SĆ­, con ggplot2 es ā€œfĆ”cilā€ hacer rĆ”pidamente grĆ”ficos de calidad, PERO dominar todos los detalles del paquete sĆ­ que es complicado, pero no nos hace falta conocerlo todo. AdemĆ”s, hay que tener en cuenta que ggplot2 es un paquete/entorno en constante evolución. Actualmente estĆ” en la versión 3.2.1. Bueno, en marzo de 2020 apareció la versión 3.3.0.

Para entender esta idea de la constante evolución y el papel que tiene la comunidad de usuarios en el desarrollo de R y sus paquetes puedes leer este tweet y las respuestas a él. En el tweet sólo se anuncia una pequeña mejora en como ggplot2 gestiona los títulos de los grÔficos pero genera reacciones en la comunidad de usuarios.

La redes sociales pueden hacerse eco de la evolución de un paquete, pero donde habitualmente se produce la discusión/colaboración entre usuarios es en plataformas como Github. Por ejemplo, puedes ver como se gestó estÔ pequeña mejora aquí. Fue la issue 3252 de ggplot2. Otro ejemplo, justo cuando estaba escribiendo este pÔrrafo, leí este otro tweet anunciando otra mejora en ggplot2.

La pĆ”gina web de ggplot2 puedes encontrarla aquĆ­. En ella puedes encontrar documentos de ayuda y la referencia oficial. Para darte cuenta de todo lo que se puede hacer con el ecosistema ggplot visita esta pĆ”gina donde podrĆ”s ver los 79 ā€œpaquetes auxiliaresā€ o extensiones a ggplot2.

Para hacer ā€œbuenosā€ grĆ”ficos con ggplot2no sólo es necesario entender la sintaxis y los pormenores del paquete, sino que quizĆ”s se necesite algo mĆ”s. Por ejemplo, algo de experiencia y cierta capacidad visual y estĆ©tica; incluso hay quien dice que hacer buenos grĆ”ficos es un arte. Para intentar mejorar vuestros grĆ”ficos o evitar ciertos errores, aquĆ­ tenĆ©is algunas reglas/consejos, y aquĆ­ un curso completo sobre visualización1 con bookdown incluido.

EntenderĆ”s muy bien que hacer buenos grĆ”ficos exige conocimiento y mucho trabajo al ver el video de este post, donde se ven las versiones previas para que al final saliese esta preciosura de gŕafico:

Otro ejemplo del making-of de un grƔfico puedes verlo aquƭ.

Otros dos libros sobre visualización con el código de los ejemplos hechos con ggplot2, aquí y aquí. Finalmente, algunos consejos de la BBC sobre visualización.

Como tambiƩn se aprende los errores, aquƭ tienes un articulo de The Economist donde muestran errores que ellos mismos han cometido haciendo grƔficos. AdemƔs pueden descargarse los datos.Otro ejemplo de grƔfico un poquito tendencioso, esta vez del Washington Post:


2. Ideas bƔsicas sobre ggplot2

Ya se dijo que ggplot2 es un paquete R desarrollado por Hadley Wickham, aunque actualmente es el resultado de la colaboración de múltiples desarrolladores. ggplot2 implementa en R The Grammar of Graphics de L. Wilkinson, un sistema coherente para describir y construir grÔficos. El énfasis de ggplot2 estÔ en la exploración rÔpida de datos, especialmente de datos de alta dimensionalidad. Con ggplot2 es sencillo ir transformando el grÔfico mientras se van analizando los datos.

Para empezar a entender la ā€œfilosofĆ­aā€ de ggplot2, os planteo una pregunta medio retórica: ĀæquĆ© vemos en el grĆ”fico de abajo?

Pues sĆ­, es un grĆ”fico de puntos y nos ayuda a ver las relaciones que existen entre 3 variables. Estamos habituados a ello, pero vamos a pensar en el grĆ”fico desde la óptica de ā€œThe Grammar of Graphicsā€ implementada en el paquete ggplot2.

En nuestro grÔfico se representan por medio de puntos, en el espacio X-Y, y mediante los distintos colores de los puntos, las observaciones de 3 variables. Bien, nada muy novedoso, todos los sistemas grÔficos hacen este tipo de grÔficos. Los grÔficos de ggplot2 se realizan mediante la superposición de elementos/capas. Podemos pensar que el grÔfico que hemos visto es una capa. ¿Cómo creamos este grÔfico o capa en ggplot2?

Pues, una de las principales ideas para entender ggplot2 es que cada capa de un grƔfico tiene 3 componentes o elementos principales:

  • los datos que se van a representar (sencillo, para hacer un grĆ”fico hacen falta datos). Para ello utilizaremos generalmente la función ggplot()

  • un conjunto de propiedades estĆ©ticas asociadas a alguna variable del conjunto de datos. Por ejemplo, la variable Sepal.Lenght estĆ” asociada al eje X, ala posición en el eje X. Por su parte, el color de los puntos (otra caracterĆ­stica visual o estĆ©tica) estĆ” asociado a los valores de la variable Species. Es decir, usando la terminologĆ­a de ggplot2, las distintas variables estĆ”n asociadas o mapeadas a determinadas caracterĆ­sticas estĆ©ticas. El mapeo de variables con estĆ©ticas se harĆ” con la función aes() de aesthetics. (esto ya no es tan estĆ”ndar, se explica en breve)

  • el elemento geomĆ©trico que se va a representar. En nuestro caso el elemento geomĆ©trico que se utiliza para representar los valores de las variables son los puntos, pero podrĆ­an haber sido las lineas o las barras … Para especificar el elemento geomĆ©trico que vamos a usar en nuestro grĆ”fico se utiliza la familia de funciones geom_xx(); por ejemplo geom_point() si queremos puntos, geom_line() si queremos que las relaciones entre las variables se representen/visualicen como lineas.

Asƭ en abstracto puede ser complicado entender del todo que quiere decir todo esto. Vamos a verlo con ejemplos concretos. De momento, para explicar las principales caracterƭsticas de ggplot2 utilizarƩ un conjunto de datos famoso, pero odiado por algunos, por haber sido utilizado en numerosos ejemplos y cursos: el iris dataset.

El conjunto de datos iris contiene datos sobre 150 flores, en concreto sobre 150 lirios. iris tiene 5 variables, 4 de ellas miden la longitud y el ancho del pétalo y sépalo de los 150 lirios. Estas 4 primeras variables son cuantitativas y continuas; mientras que la quinta variable es categórica, indicando la clase o variedad de los lirios, ya que en los datos hay 3 especies distintas de lirios (setosa, versicolor y virginica). Con estos datos, con 3 de sus variables, se ha creado el grÔfico que ves mÔs arriba.


PRIMER GRÁFICO: Para comenzar nuestras andanzas con ggplot2 intentaremos replicar con código R el grÔfico de arriba; aunque al principio sólo utilizaremos 2 variables: haremos un grÔfico de puntos de la longitud del sépalo frente a la longitud del pétalo.

Hacer un grƔfico con ggplot2 requiere de varias etapas,

  • la primera de ellas consiste en usar la función ggplot() para inicializar el grĆ”fico.

  • en segundo lugar, tendremos que especificar que conjunto de datos usaremos en el grĆ”fico.

  • en tercer lugar tendremos que especificar que variables irĆ”n asociadas a determinados elementos visuales o estĆ©ticos del grĆ”fico

  • por Ćŗltimo, en cuarto lugar, tendremos que especificar que tipo de grĆ”fico o geometrĆ­a usaremos para visualizar las observaciones.

VeƔmoslo mƔs detenidamente.

En ggplot2, para hacer un grĆ”fico se empieza SIEMPRE llamando a la función ggplot(). Si tecleamos ggplot() en la consola o en un script, parece que no ocurre nada, pero tras la llamada a la función ggplot(), R ha creado un objeto, un contenedor para nuestro futuro grĆ”fico. AĆŗn no vemos el grĆ”fico, faltan cosas, pero ya lo hemos inicializado. Si quieres ver el objeto/contenedor que hemos creado con la llamada a ggplot() tienes que asignarle un nombre, asĆ­ podrĆ”s verlo en la pestaƱa ā€œEnvironmentā€ de RStudio.

Para verlo tienes que hacer:

my_grafico <- ggplot()

my_grafico, es un objeto R, concretamente una lista con 9 elementos, que tendremos que ir ā€œllenandoā€ para hacer nuestro grĆ”fico.

Generalmente, dentro de la función ggplot() se suele especificar el conjunto de datos que vas a utilizar para hacer el grÔfico. A diferencia de los grÔficos de R-base, ggplot2 no permite graficar vectores: los datos que se suministran han de ser SIEMPRE data.frames o similares.

ggplot(data = iris)
ggplot(iris)

Ya estƔ, con cualquiera de las 2 instrucciones de arriba, son equivalentes2, ya hemos inicializado el grƔfico y le hemos dicho que datos vamos a usar.

Dijimos que para comenzar haríamos un grÔfico de puntos de la variable Sepal.Length frente a Petal.Length; así que tenemos que decirle a ggplot2 que variables de iris queremos visualizar y con que propiedades estéticas queremos asociar cada variable. Para ello utilizaremos la función aes() dentro de ggplot(). Lo hacemos con la siguiente expresión:

ggplot(iris, aes(x = Sepal.Length,  y = Petal.Length))

Aún no vemos el grÔfico, pero con aes() le hemos dicho a R/ggplot2 que queremos asociar/mapear la variable Sepal.Length al eje x, y la variable Petal.Length al eje y. Fíjate que, en la pestaña de grÔficos de RStudio, ya vemos los ejes del grÔfico; ademÔs, fíjate que ggplot2 ya ha calculado para nosotros el rango de las variables para ajustar los ejes.

Al igual que antes, podemos omitir los nombres de las opciones de la función aes(). Esta función puede tener muchos argumentos (veremos algunos), pero los 2 primeros son siempre x (el eje x) y después y (el eje y o vertical de mi grÔfico); es decir podríamos hacer lo siguiente que es mÔs rÔpido de teclear.

ggplot(iris, aes(Sepal.Length, Petal.Length))

Con esta instrucción le estamos diciendo a ggplot2 que vamos a hacer un grĆ”fico con los datos del data.frame iris y que vamos a asociar/conectar/mapear la variable Sepal.Length con el eje x, y la variable Petal.Length con el eje y del grĆ”fico. Perfecto, pero entonces Āæpor quĆ© no vemos el grĆ”fico? La razón estriba en que no le hemos dicho a ggplot2 quĆ© tipo de grĆ”fico queremos (de puntos, de lineas etc…). El tipo de grĆ”fico se explicita con una familia de funciones: geom_xx() o geometrĆ­as. Hay muchas geometrĆ­as o tipos de grĆ”ficos que podemos usar. Ya lo veremos!!! Nosotros queremos hacer un grĆ”fico de puntos, asĆ­ que, de la familia de geometrĆ­as tenemos que usar geom_point(). VeĆ”moslo.

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point()

Como vemos los puntos del grƔfico se visualizan en color negro, con un tamaƱo, una transparencia y una forma determinadas. Obviamente todo esto se puede cambiar dentro de geom_point() con las opciones adecuadas. Por ejemplo:

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point(color = "red", size = 2, alpha = 0.2)

Ya casi estÔ. No es tan complicado. Ya hemos visto las ideas fundamentales de la visualización con ggplot2:

  1. Los grÔficos se inician con la función ggplot(). Generalmente aquí se especifican los datos (data.frame) que queremos utilizar

  2. La función aes() sirve para asociar/mappear variables con atributos/caracterĆ­sticas estĆ©ticas del grĆ”fico. De hecho el nombre del función aes() viene de aesthetics. Las aesthetics mĆ”s importantes de un grĆ”fico suelen ser los ejes x e y, por eso se ponen siempre al principio de aes(); es decir, son los 2 primeros argumentos de aes(). En nuestro ejemplo hemos asociado la variable Sepal.Length con el eje x, y la variable Petal.Length con el eje y. Veremos mĆ”s aesthetics, como por ejemplo el color o el tamaƱo (de los puntos … o de las lineas o …)

  3. Con geom_**() elegimos el tipo o geometrĆ­a de grĆ”fico. Hay muchos tipos de grĆ”ficos, asĆ­ que habrĆ”n muchos geoms. Por ejemplo: geom_point(), geom_line(), geom_ …

Estas 3 ideas son las principales para entender ggplot2. DespuĆ©s hay muchas mĆ”s opciones y elementos que serĆ”n muy importantes para conseguir un buen grĆ”fico, pero en cierta forma son secundarias; por ejemplo, los tĆ­tulos, los ejes, las escalas, el tema etc… lo iremos viendo poco a poco.


Afiancemos las ideas principales de la visualización con ggplot2. ¿Piensa que harÔ la siguiente linea de código?

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_line()

La primera parte de la instrucción es igual a la anterior: queremos un grÔfico con el data.frame iris y queremos asociar la variable Sepal.Length con el eje x, con la propiedad aesthetic eje x y Petal.Length con el eje y; pero le hemos pedido un grÔfico de lineas (geom_line()). En este caso hacer un grÔfico de lineas no tiene mucho sentido, pero si se lo pedimos a R, este nos hace caso y nos lo muestra.


Ya dije que una de las características importantes de ggplot2 es que funciona por capas que se van superponiendo; para ir añadiendo capas a nuestro grÔfico tenemos que usar el símbolo +. Por ejemplo si quisiéramos ver los puntos y las lineas ¿Cómo lo hacemos?

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_line()


Tampoco son muy útiles la lineas en este grÔfico.

Otra geometrƭa o geom_() que se usa mucho es geom_smooth(). ProbƩmosla:

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() +  geom_smooth()

Del grÔfico puede inferirse que hay una relación, no lineal, pero sí directa o positiva entre la longitud del sépalo y del pétalo; pero también se aprecia que hay al menos dos grupos distintos de lirios. Hay un grupo de observaciones cuyo pétalo parece ser claramente menor que el del resto de lirios. Veamos si esto se debe o esta asociado al tipo de lirio, recuerda que hay 3 tipos de lirios, asociados a la variable iris$Species. Para verlo en nuestro grÔfico lo que vamos a hacer es asociar/mapear la variable Species con la aesthetics color:

ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point() 

Pues parece que sĆ­, que la especie de lirios ā€œsetosaā€ es mĆ”s pequeƱa, al menos en longitud, del sĆ©palo, pero sobre todo del pĆ©talo.

La variable Species podríamos haberla asociado a la estética tamaño (size), o a la estética forma (shape) pero no sería tan útil ni quedaría tan bonito el grÔfico. Fíjate que incluso R nos avisa de que asociar la propiedad o aesthetic tamaño con una variable categórica como Species no es muy recomendable.

ggplot(iris, aes(Sepal.Length, Petal.Length, size = Species)) + geom_point() 
#> Warning: Using size for a discrete variable is not advised.

TambiĆ©n podemos asociar la variable Species a la estĆ©tica ā€œformaā€(shape). Lo hacemos en la expresión de mĆ”s abajo. Como veis, ahora las diferencias entre especies de lirios no se aprecian tan bien como cuando usĆ”bamos color = Species

ggplot(iris, aes(Sepal.Length, Petal.Length, shape = Species)) + geom_point() 

Os va a costar hacer grƔficos, normal!!!, pero espero que la idea principal ya la tengƔis. Lo que pasa es que no os he contado todo, en realidad es un poco mƔs complejo y versƔtil. Lo medio explico en el siguiente apartado.


MƔs ideas sobre ggplot2

Ya tenƩis las ideas principales para hacer grƔficos con ggplot2, pero no os lo he contado todo, tampoco lo voy a hacer ahora, pero si contarƩ las cosas de una forma diferente para que tengƔis mƔs flexibilidad/versatilidad a la hora de hacer grƔficos. Si querƩis saber toda la verdad3 tendrƩis que ir al libro de Hadley, concretamente aquƭ.

La forma que os he contado de hacer grƔficos ggplot2 es la que verƩis habitualmente, yo tambiƩn hago mis grƔficos asƭ, solo que para entender mejor el funcionamiento, la sintaxis de ggplot2, os lo tengo que contar otra vez de una forma un poco diferente o ampliada.

Un grĆ”fico de ggplot2 se inicia llamando a la función ggplot() eso es cierto y tambiĆ©n es verdad que generalmente dentro de ggplot() se indica el data.frame que vas a utilizar y con aes() que variables vas a usar y con que elementos visuales o estĆ©ticos quieres asociar cada una de la variables que vas a utilizar4. Correcto, pero …. en realidad un grĆ”fico ggplot2 se hace por capas, cada capa se especifica con una función de la familia geom_xx(), asĆ­ que en realidad los datos y las aes() se ā€œdeberĆ­anā€ especificar dentro de la función geom_xx()`.

Parece un poco de lío pero en cuanto lo entiendas es muy fÔcil y te puede dar mÔs flexibilidad a la hora de hacer tus grÔficos. Empecemos: ¿recuerdas que hacen las lineas/expresiones de abajo?

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() 
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_line()

Podemos pensar que las funciones que hacen la representación grÔfica realmente son las geom_xx(); es ahí donde deberíamos especificar los datos y variables/estéticas que queremos usar, pero si no las especificamos en la función geom(), entonces, ggplot2 mirarÔ a ver si existen, si estÔn especificados, dentro de ggplot().

Ahora que ya sabemos el funcionamiento bÔsico de ggplot2, veamos algunos detalles mediante algunos ejemplos. Hasta ahora hemos especificado el data.frame que queremos graficar con ggplot(data = my_df) o con ggplot(my_df) y las variables que queremos ver, y a que propiedad estética queremos asociarla, con la función aes() dentro de ggplot(). Si lo hacemos así, todos los geoms_xx() que utilicemos compartirÔn el conjunto de datos y las variables/estéticas a mappear y mostrar; pero a veces, en grÔficos mÔs complejos podemos querer hacer que cada geom_xx() muestre datos y/o variables distintas.

Entender que cada geom_xx() puede estar asociado a distintos data.frames y/o variables es importante para tener mĆ”s versatilidad con ggplot2.Por ejemplo, las siguientes tres expresiones hacen el mismo grĆ”fico. Se suele utilizar la primera expresión, pero la segunda y tercera expresiones son ā€œmĆ”s flexiblesā€, aunque es verdad que si sólo se utiliza un geom_xx() no ganamos nada por usar la segunda o tercera expresión, pero no serĆ” el caso si en nuestro grĆ”fico necesitamos usar varios geom_xx()

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point()

ggplot(iris) + geom_point(aes(Sepal.Length, Petal.Length))

ggplot() + geom_point(data = iris, aes(Sepal.Length, Petal.Length))

Fíjate, como detalle, pero importante, que si utilizas la tercera expresión; es decir, si especificas los datos dentro de la función geom_xx(), es necesario poner el nombre del argumento ; es decir, debes poner data = iris, no puedes poner solo iris. Yo me olvido siempre de este detalle5.

En este caso (como el grÔfico solo tiene una capa, como sólo usamos un geom(_xx)) no ganamos nada por usar la segunda o tercera expresión; PERO, cuando usemos varios geom_xx() esto nos darÔ muchas posibilidades para nuestro grÔfico.

Intenta descubrir las diferencias y funcionamiento de las 3 siguientes instrucciones. Recuerda que puedes correr las instrucciones en R para ver que hacen exactamente.

ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point() + geom_smooth()
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point(aes(color = Species)) + geom_smooth()
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_smooth(aes(color = Species))

VeƔmoslas una a una:

  1. Los datos y las 3 variables/estƩticas dentro de ggplot()
ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point() + geom_smooth()

En este caso los 2 geoms comparten el conjunto de datos (iris) y las variables/estƩticas a graficar


  1. Los datos y 2 variables/estƩticas dentro de ggplot(), pero una tercera variable (Species), asociada a la estƩtica color, aparece solamente en geom_point()
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point(aes(color = Species)) + geom_smooth()


  1. Los datos y 2 variables/estƩticas dentro de ggplot(), pero una tercera variable (Species), asociada a la estƩtica color, aparece solamente en geom_smooth()
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_smooth(aes(color = Species))

Espero, seguro!!, que te has dado cuenta de que si especificas el data.frame y las variables/estƩticas dentro de ggplot() esto afectarƔ a todos los geoms del grƔfico; pero lo que se especifique dentro de un geom_xx() solo afecta a esa geometrƭa.


Otro ejemplo para entenderlo, ¿por qué no funciona la siguiente expresión?

ggplot(iris) + geom_point(aes(Sepal.Length, Petal.Length)) + geom_smooth(aes(color = Species))

Pues porque para poder representar la linea suavizada se utiliza geom_smooth(), y geom_smooth() necesita como mĆ­nimo tener variables asociadas a las estĆ©ticas x e y. Como veis, dentro de geom_smooth() solo hemos especificado la estĆ©tica ā€œcolorā€ y tampoco hemos especificado en la función ggplot() que variables se asocian con x e y. Por lo tanto, geom_smooth() no puede hacer su trabajo, le faltan los ā€œdatosā€ de x e y para calcular/obtener la linea suavizada.

Vamos con otros ejemplos. La siguientes expresiones tampoco funcionarÔn6 si intentÔis correrlas en vuestro ordenador. ¿Por qué?

ggplot() + geom_point(data = iris, aes(Sepal.Length, Petal.Length)) + geom_line(aes(Sepal.Length, Petal.Length))

ggplot(aes(Sepal.Length, Petal.Length)) + geom_point(data = iris) + geom_line()

ggplot() + geom_point(data = iris, aes(Sepal.Length, Petal.Length)) + geom_line()


Otro ejemplo: hagamos algo mÔs marciano/complicado. Supón que quieres hacer un grÔfico diferenciando los puntos por color para las tres especies de lirios, pero quieres que solo se vea la linea suavizada para las dos especies mÔs grandes (virginica y versicolor). Los lirios mÔs pequeños son los de la clase setosa. Igual se puede hacer de otra forma pero la que me viene a la cabeza es hacer lo siguiente:

Primero, crear un dataset que sólo contenga a los lirios grandes, los de las especies virginica y versicolor.

iris2 <- iris %>% filter(Species != "setosa") #- me quedo con los lirios que no son de clase "setosa"

Para despuƩs hacer el grƔfico con cualquiera de las 2 expresiones siguientes. Prefiero la segunda porque hay que teclear/escribir menos, pero puede que sea mƔs didƔctica la primera.

ggplot() + geom_point(data = iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_smooth(data = iris2, aes(Sepal.Length, Petal.Length) ) 

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point(aes(color = Species)) + geom_smooth(data = iris2)


Otro ejemplo mÔs: ¿y si quisiéramos que las 2 especies grandes se representen con el mismo color? Hay varias soluciones, una de las mÔs marcianas es la que propongo abajo. Es una solución rara, pero creo que os ayudarÔ a entender ggplot2

Primero voy a crear un nuevo data.frame sólo con las observaciones de los lirios pequeños, los de la clase setosa.

iris_setosa <- iris %>% filter(Species == "setosa") #- me quedo con los lirios pequeƱos, los de clase "setosa"
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_point(data = iris_setosa, aes(color = Species)) + geom_smooth(data = iris2,aes(Sepal.Length, Petal.Length) )

Otra solución, quizÔs mÔs lógica, consiste en primero agrupar las 2 especies de lirios grandes (versicolor y virginica) en una sola clase.

iris_solo_2_clases <- iris %>% mutate(Species_2 = ifelse(Species %in% c("versicolor", "virginica"), "versi_virgi", "setosa"))

Para después hacer el grÔfico. AdemÔs el grÔfico lo podemos hacer al menos de 2 maneras, la segunda mucho mejor, la primera expresión es un poco enrevesada:

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_point(data = iris_setosa, aes(color = Species)) + geom_smooth(data = iris_solo_2_clases,aes(Sepal.Length, Petal.Length, color = Species_2) )

ggplot(iris_solo_2_clases, aes(Sepal.Length, Petal.Length, color = Species_2)) + geom_point() + geom_smooth()

Como veis, en ggplot2 hay varias maneras de hacer el mismo grƔfico. Esto al principio puede abrumar/molestar, pero muestra la flexibilidad de la sintaxis.

Para ir acabando con la ā€œfilosofĆ­aā€/sintaxis/gramĆ”tica de ggplot2 intenta imaginar que grĆ”ficos hacen las 6 expresiones de mĆ”s abajo.

Si no puedes, recuerda que siempre puedes ejecutar las ordenes en el ordenador. Fíjate sobre todo en la tercera expresión

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_smooth()
ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point() + geom_smooth()
ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point(color = "purple") + geom_smooth()

ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point() + geom_smooth(color = "brown")
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_smooth(aes(color = Species))
ggplot(iris) + geom_point(aes(Sepal.Length, Petal.Length, color = Species) ) + geom_smooth(aes(Sepal.Length, Petal.Length, color = Species))


En la tercera expresión se especifica color = Species dentro de aes() en ggplot(), así que, de momento, todos los geoms del grÔfico deberían diferenciar por especies de lirios usando el color, PERO, después se vuelve a usar el argumento color dentro de geom_point(), pero fíjate que no va dentro de de aes(), va fuera. Concretamente hacemos lo siguiente: geom_point(color = "purple"); es decir, para la capa de puntos, y solo para la capa de puntos que se crea con geom_point(), estamos asociando la estética color, no a una variable, sino a un color fijo. Sin embargo, para la otra capa del grÔfico, la que resulta de usar geom_smooth() sigue siendo valido que la estética color estÔ asociada a la variable Species.

Un detalle: imagina que en un grƔfico en el que has fijado 3 estƩticas dentro de ggplot(aes()). En principio las 3 estƩticas afectarƔn a todos los geom_xx() que utilices en tu grƔfico, pero si quisieras que, por ejemplo, la estƩtica color no afectase a un geom concreto, podrƭas hacer lo siguiente: geom_xx(aes(color = NULL)).

Como puedes imaginar, aĆŗn tenemos que ver mĆ”s elementos de ggplot2. Como mĆ­nimo los tĆ­tulos y leyendas, los ejes, el tema, coordenadas, etc… vamos a ello!!


3. Elementos de un ggplot

Ya hemos presentado los principales elementos de los grÔficos hechos con ggplot2, los que tienen que ver con la representación de las variables. Pero es evidente que un grÔfico tiene muchos mÔs elementos, y lógicamente hay que conocerlos un poco para poder ajustar los grÔficos a nuestras necesidades y mejorar la calidad de nuestros grÔficos.

Ejemplos de otros elementos son: tĆ­tulos del grĆ”fico y de los ejes, ā€œthemeā€ del grĆ”fico, small multiples o faceting, anotaciones etc…

En estÔ sección iremos mÔs rÔpido. Se presentarÔn solamente algunos ejemplos, conceptos y/o aclaraciones. Si necesitas profundizar mÔs en estos elementos, puedes acudir a la referencia oficial de ggplot2 o al bokkdown de ggplot2.

Ya dijimos que los grƔficos ggplot se componen de capas o layers. Para nosotros, hasta ahora, una capa estaba compuesta de 3 elementos:

  • un conjunto de datos

  • un conjunto de variables mapeadas con aes() a propiedades estĆ©ticas

  • una geometrĆ­a, con geom_xx()

Es evidente que en todos los geoms no se pueden especificar todas las caracterƭsticas estƩticas. Por ejemplo si usas geom_point no podrƔs especificar la anchura o el tipo de las lineas, porque no estƔs usando lineas sino puntos. Para ver que estƩticas admite cada geom tendrƔs que mirar la ayuda de cada geom. Al final de este post tienen un grƔfico interactivo con el que se puede ver fƔcilmente que caracterƭsticas estƩticas admite cada geom. Por ejemplo geom_bar(), que sirve para hacer grƔficos de barras, no admite mappear variables al eje Y, ya que en el eje Y se visualizan/mapean las frecuencias absolutas o relativas de la variable que se representa en el eje X.


Esto es lo bÔsico que hay que saber, pero en realidad, una capa necesita de dos elementos mÔs: una stat (o transformación estadística) y una posición. Estos dos últimos elementos son necesarios pero la verdad es que podríamos seguir haciendo grÔficos con ggplot2 sin conocerlos. ¿Por qué? Pues porque si en una capa no los especificamos, lo hace ggplot2 por nosotros. Lo ha estado haciendo hasta ahora en todos los grÔficos que llevamos hechos. Pero claro, saber como utilizar estos elementos nos darÔ mÔs flexibilidad a la hora de hacer ggplots.

Generalmente las capas se van aƱadiendo con la familia de funciones geom_xx(), PERO tambiƩn se pueden aƱadir capas con otra familia de funciones stat_xx().

Aparte de estos cinco elementos (datos, aes(), geom, stat y posición) los grÔficos ggplot pueden tener mÔs elementos. VeÔmoslos uno a uno.


3.1 Tƭtulos del grƔfico

Es evidente que un grÔfico para ser efectivo y mostrar su mensaje con claridad debe tener un título y/o subtítulo ilustrativo y debe mostrar información relevante sobre que variables se grafican los ejes X e Y. Este tipo de elementos pueden modificarse de varias maneras, pero nos centraremos en la función labs().

Fíjate que con la función labs(), de labels, podemos cambiar los títulos del grÔfico, de los ejes y también de las leyendas.

En los títulos (tanto del grÔfico, como de los ejes y leyendas) también se pueden cambiar otras características; por ejemplo, cambiar el tamaño, la fuente o el color, pero eso serÔ tarea de otra función de ggplot2: del grupo de funciones theme_(). Pero el tema o theme de los grÔficos lo veremos en el siguiente apartado.

Tomemos el siguiente grƔfico como referencia y sobre Ʃl iremos aƱadiendo elementos:

p <- ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point()
p

Con la función labs() podemos añadirle un título, subtitulo, pie de grÔfico o caption. También podemos cambiar el título de los ejes X e Y, así como también el titulo de la leyenda para color, o para otras estéticas que utilicemos en el grÔfico.

Es suficiente con ver un ejemplo:

p + labs(title = "GrƔfico 1: Longitud del sƩpalo frente al pƩtalo",
       subtitle = "(diferenciando por especie de lirio)",
       caption = "Datos provenientes del Iris dataset",
       x = "Longitud del sƩpalo",
       y = "Longitud del pƩtalo",
       color = "Especie de lirio")

Si quisieras eliminar completamente los títulos del eje X podrías hacerlo en el anterior chunk fijando x = NULL dentro de la función labs().

En lugar de usar la función labs(), también podemos utilizar las funciones auxiliares xlab() e ylab()

p + labs(color = NULL, x = NULL)  #- borra el tĆ­tulo de la leyenda y del eje X
p + xlab(NULL) + ylab(NULL)       #- elimina tĆ­tulos de los ejes X e Y

3.2 Themes

Themes control the display of all non-data elements of the plot. You can override all settings with a complete theme like theme_bw(), or choose to tweak individual settings by using theme() and the element_ functions. Use theme_set() to modify the active theme, affecting all future plots.

Para cambiar detalles de la apariencia del grĆ”fico como el tamaƱo, fuentes y color de los tĆ­tulos, pero tambiĆ©n de los puntos, las lineas, el fondo del grĆ”fico, la apariencia de las grid-lines, el lugar para las leyendas, etc… etc… contamos con las ā€œfunciones de temaā€; todas ellas comienzan con theme_()

En general con las funciones theme_() podemos cambiar/ajustar cualquier elemento del grÔfico, con la excepción de la propia representación de los datos (ya sabemos que esto se hacen con las funciones geom_()). Estos elementos afectan a la apariencia y detalles del grÔfico, pero no a la relación entre variables que se muestra realmente en el grÔfico.

Para empezar a entender que hacen las funciones relacionadas con el theme, seƱalar que ggplot2 incorpora un conjunto de ā€œtemasā€ que podemos utilizar para cambiar la apariencia del grĆ”fico a nuestro gusto. Puedes verlos todos aquĆ­. El tema que usa por defecto ggplot2 es theme_gray(). Veamos a los themes en acción:

p + theme_gray()   #- tema por defecto
p + theme_light()
p + theme_dark()
p + theme_classic()
p + theme_minimal()
p + theme_void()

El paquete de R ggthemes incorpora una amplia lista de temas adicionales, algunos de ellos tratan de replicar el estilo de corporaciones famosas como The Economist o Stata. En el tutorial no se ven bien los grÔficos porque los he hecho pequeñitos, pero prueba a hacerlos tú mismo y verÔs que son good-looking. Veamos algunos:

library(ggthemes)
p + theme_economist()   
p + theme_fivethirtyeight()
p + theme_stata()
p + theme_solarized()

TambiƩn podemos definir un tema propio para que el grƔfico se ajuste los mƔs posible a nuestras preferencias.

# define custom theme
my_theme <- theme(axis.text.x = 
                  element_text(colour = "grey20", size = 12, angle = 90, hjust = 0.5, vjust = 0.5) ,                   axis.text.y = element_text(colour = "grey20", size = 12) ,
                  text = element_text(size = 16))
p + my_theme

Se puede fijar el tema/theme de los grÔficos con la función theme_set(). Por ejemplo:

theme_set(theme_minimal())  #- un tema concreto
theme_set(theme_minimal() + 
    theme(axis.text.x=element_blank(), axis.ticks.x=element_blank())) #- un tema modificando algunas opciones, que ele eje x no muestre ticks ni escalas

Si quieres volver al theme por defecto:

theme_set(theme_gray())

Ejemplos de algunos elementos cuya apariencia que se pueden cambiar con theme()

p + theme(legend.position = "none")            #- que no aparezca leyenda
p + theme(legend.position = "bottom")          #- leyenda abajo
p + theme(legend.direction = "horizontal")     #- leyenda horizontal!!
p + theme(legend.title = element_text(size = 22))     #- tĆ­tulo de la leyenda a 22
p + theme(legend.key.size = unit(2.4, "cm"))          #- tamaƱo de los cuadros de la leyenda


p + theme(text = element_text(size = 20, face = "bold"))         #- cambiar el tamaƱo de todos los elementos de texto
p + theme(text = element_text(face = "bold"))                    #- pone en negrita todos los elementos de texto

p + theme(axis.text.x = element_text(colour = "pink", size = 12, angle = 90, hjust = 0.5, vjust = 0.5)) # apariencia de la escala del eje x

p + theme(axis.title.y = element_text(size=25, angle = 45)) #- tamaƱo y angulo del texto del eje Y

p + theme(plot.subtitle = element_text(hjust = 3))   #- posición horizontal del subtitulo (si lo tuviese)
  
p + theme(plot.caption = element_text(hjust = 3))    #- posición vertical del pie de grÔfico (si lo tuviese)

p + theme(panel.background = element_rect(fill = "green", colour = "pink", linetype = "longdash", size = 3.5))
p + theme(panel.background = element_blank())
p + theme(panel.background = NULL)


p + theme(plot.background = element_rect(fill = "pink", colour = "purple", linetype = "dotted", size = 7))

Si quieres ver todos las características que controla y que por tanto puedes modificar con theme(), usa la ayuda de la función theme() o ejecuta en R args(theme). Aunque casi mejor verlas en esta infografía de Henry Wang o en este post de Isabella Benabaye con las opciones que suele cambiar ella.

En general, si quieres cambiar algún elemento de un ggplot, has de hacer theme(elemento = element_text()). Si quieres eliminar por completo algún elemento del grÔfico, por ejemplo las grid-lines del grÔfico, harías theme(panel.grid = element_blank()).

Evidentemente todo esto es imposible de aprender, sólo tienes que saber que cualquier elemento del grÔfico se puede cambiar y tienes que saber buscar e interpretar la ayuda.

Colores

Se poco de colores, pero claro si quieres cambiar la apariencia de los grƔficos, has de saber los colores de R, asƭ que:

  • AquĆ­ tienes una guĆ­a para elegir color. Si solo quieres ver la lista de nombres de los colores en R ejecuta: aa <- as.data.frame(colours())

  • Si sabes el nombre del color que quieres, aquĆ­, podrĆ”s buscarlo y ver su color y su codificación en RGB y Hex.

  • TambiĆ©n es interesante el paquete paleteer que agrupa un conjunto amplio de paletas de colores para usar en R.


XKCD theme

Por último, no sé si conocéis el webcomic XKCD. Pues en R también hay un paquete y un theme para hacer grÔficos al estilo XKCD. Es el paquete xkcd cuyo autor es Emilio Torres-Manzanera de la Universidad de Oviedo. Aquí podéis ver algunos grÔficos hechos con este estilo en R.

Intenté hacer un grÔfico con su theme, pero desafortunadamente no me salió; pero justo al día siguiente vi este tweet que hace algo parecido con datos de los Simpsons y su código sí me ha funcionado, ademÔs simula/construye el estilo XKCD desde cero.

AdemÔs, después vi que Evangelyne Reinolds hizo esta maravilla. Lo haremos en clase?! En este post puedes encontrar el código para reproducir una de las historias o viñetas de XKCD.


3.3 Small multiples o Facetting

El sistema grĆ”fico de ggplot2 incorpora una tĆ©cnica especial llamada ā€œfacetingā€ que permite dividir un grĆ”fico en mĆŗltiples grĆ”ficos. Cada uno de esos mĆŗltiples grĆ”ficos se realiza sólo para las observaciones de una de los valores de una variable categórica (o factor) incluido en el conjunto de datos. Es mĆ”s fĆ”cil hacerlo que explicarlo/escribirlo.

Por ejemplo, en iris tenemos la variable Species, que es categórica. Lo que se hace con el ā€œfacettingā€ es dividir el dataset en grupos y hacer el mismo grĆ”fico para cada uno de los grupos. Los grupos se van a definir en función de los valores de la variable Species. Recuerda que hay tres tipos o especies de lirios.

Para hacer un ā€œfacetting graphā€ podemos usar las funciones facet_wrap() y facet_grid().

Por ejemplo, con la función facet_grid() puedes elegir entre hacer los small multiples por filas o por columnas. Empecemos haciendo un facetting por columnas. AdemÔs, con facet_grid() se pueden usar varias sintaxis, pero la que aparece en la cheatsheet actual de ggplot2 y, por tanto, la recomendada es las que ves en la segunda linea:

#p + facet_grid( . ~ Species)                # old sintaxis
p + facet_grid(cols = vars(Species))         # grƔficos x columnas, separando por valores de 'Species'

Ahora por filas:

p + facet_grid(rows = vars(Species))         # grƔficos x filas

También podemos utilizar la función facet_wrap(). Esta función reparte los small multiples en una rejilla con forma de matriz.

p + facet_wrap(vars(Species), nrow = 2, ncol = 2)        # graf x filas y columnas

Si en el dataset hubiesen dos variables categóricas podríamos hacer que una de ellas sirviese para llenar las filas y la otra las columnas.

Como iris sólo tiene una variable categórica (Species) vamos a discretizar una de las variables continuas. Por ejemplo la anchura del pétalo, crearemos una nueva variable dividiendo las observaciones de Petal.Width en 2 categorías, por encima y por debajo de la media de su media. AdemÔs lo vamos a hacer con R-base y con dplyr::ntile(). Seguro que hay mejores formas, por ejemplo

Con dplyr::ntile()

iris <- iris %>% mutate(new_variable = ntile(Petal.Width, 2)) 

Con R-base y la función cut():

iris <- iris
iris$new_variable <- cut(iris$Petal.Width, 
                   breaks = c(-Inf, mean(iris$Petal.Width), Inf), 
                   labels = c("debajo-media", "arriba-media"))

Ahora ya tenemos dos variable discreta y podemos hacer que facet_grid() utilice una variable para llenar filas y otra para columnas. Se puede especificar de dos maneras

ggplot(iris) + geom_point( aes(Sepal.Length, Petal.Length, color = Species)) +
facet_grid(rows = vars(new_variable), cols = vars(Species))        # graf x filas y columnas
ggplot(iris) + geom_point( aes(Sepal.Length, Petal.Length, color = Species)) +
facet_grid(new_variable ~ Species)     

Como vemos, los lirios de la clase setosa siempre tiene el ancho de su pƩtalo por debajo de la media, y los virginica siempre estƔn por encima de la media.

Ejes de los small multiples

Podemos ajustar las escalas de los ejes para que sean comunes para cada small multiple (la opción por defecto) o dejar que las escalas de cada grÔfico varíen en función del rango de los datos representados:

p + facet_grid(rows = vars(Species))    #- escalas comunes
p + facet_grid(rows = vars(Species), scales = "free")   #- las escalas de cada small pueden variar
p + facet_grid(rows = vars(Species), scales = "free_y") #- solo dejamos libre/variar la escala del eje y

Solo muestro el resultado de la segunda expresión:

Un truquito: el argumento margin en ggplot2::facet_grid() añade margenes a los small multiples facilitando la visualización. AdemÔs añade un nuevo small con todas las observaciones.

Puedes probarlo tĆŗ mismo corriendo lo siguiente:

p + facet_grid(rows = vars(Species),  margins = TRUE)   

Otro truco, esta vez avanzado: Un gist para poner labels a los smalls multiples.


3.4 Anotaciones

Annotations are a special type of layer that don’t inherit global settings from the plot. They are used to add fixed reference data to plots.

Las anotaciones en los grÔficos permiten resaltar algún fenómeno u observación de interés, y son importantes a la hora de contar historias (storytelling) con los grÔficos y visualizaciones.

En el entorno ggplot podemos hacer anotaciones en nuestro grƔficos de varias maneras, por ejemplo con annotate(). Aunque conceptualmente, como seƱala Hadley, las anotaciones son metadatos, desde el punto de vista prƔctico se usan los mismas funciones o geoms para manipularlos.

TambiƩn existen algunas funciones auxiliares en ggplot2 y en paquetes especƭficos para hacer anotaciones en grƔficos ggplot. Por ejemplo, cuando se hacen anotaciones en un grƔfico de puntos es fƔcil que las anotaciones caigan unas encima de otras, el paquete ggrepel permite aliviar este problema.

Utilicemos la función annotate(). Por ejemplo, el siguiente chunk hace algunas anotaciones sin mucho sentido pero fÔciles de entender:

Por ejemplo, el siguiente chunk usa annotate() para hacer algunas anotaciones sin mucho sentido, pero fƔciles de entender:

p + annotate(geom = "text", x = 6, y = 2, label = "Una anotación", size = 5) +
    annotate("rect", xmin = 6, xmax = 7,ymin = -Inf, ymax = Inf, alpha = 0.2, fill = "pink") + 
    annotate("segment", x = 5, xend = 7, y = 6, yend = 8, colour = "blue") 


Anotaciones de texto en las observaciones

Agregar texto a un grÔfico es una de las formas mÔs comunes de anotación. Por ejemplo, para señalizar e identificar observaciones anómalas. Sin embargo, como señala Hadley, añadir texto no es fÔcil por la forma en la que R maneja las fuentes.

La función principal para el etiquetado de grÔficos es geom_text(). Por ejemplo:

p + geom_text(aes(label = Species))

TambiƩn podƭamos haber aƱadido el valor de la longitud del pƩtalo.

p + geom_text(aes(label = Petal.Length))

Hemos añadido a cada observación un texto, proveniente de alguna de las variables de iris. Este grÔfico no es muy útil, pero la técnica sí. Imagina que queremos marcar los lirios 45 y 1407. Podemos hacer lo siguiente:

#- seleccionamos los lirios mƔs grandes de cada especie 
iris_max <- iris %>% group_by(Species) %>% slice_max(Petal.Length, n = 1)

p + 
 geom_text(data = iris_max, aes(label = Species), color = "black", size = 3)

Podemos ajustar la posición y tamaƱo del texto, etc.. Por ejemplo, podemos cambiar la alineación de las anotaciones con con hjust(ā€œleftā€, ā€œcenterā€, ā€œrightā€, ā€œinwardā€, ā€œoutwardā€) y vjust (ā€œbottomā€, ā€œmiddleā€, ā€œtopā€, ā€œinwardā€, ā€œoutwardā€).


Lineas

Podemos aƱadir lineas:

p + geom_vline(xintercept = 6)
p + geom_hline(yintercept = 5, size = 1.7, colour = "purple", linetype = "dashed")
p + geom_abline(intercept = 0.7, slope = 0.4, size = 1.9, colour = "steelblue")


3.5 Cambiando los lĆ­mites de los ejes

Si quieres modificar el recorrido de los ejes, los ā€œlĆ­mitesā€ de los ejes, puedes usar lims(). Para los ejes X e Y hay dos funciones auxiliares: xlim() e ylim().

p + lims(color = c("setosa"), x = c(NA,6), y = c(1,8))

p + xlim(c (4, 6)) + ylim(c(NA, 5)) 

Se puede hasta dar la vuelta a los ejes

p + xlim(c (7, 3)) + ylim(c(NA, 5)) 

Los lƭmites o dominio del grƔfico suelen obtenerse automƔticamente de los datos, pero, otra vez according to Hadley, hay dos razones por las que podemos estar interesados en cambiar los lƭmites del grƔfico:

  1. centrarnos en una región especifica del grÔfico

  2. aumentar los lƭmites para que varios grƔficos ajusten sus escalas.


Por ejemplo, si después de hacer un grÔfico quieres centrarte sólo en una parte; es decir, hacer un zoom sobre una parte del grÔfico, tenemos 2 alternativas:

  1. Borrar los puntos que caen fuera de los limites de lo que quieras que se visualice (si en una escala continua solo quieres usar un lĆ­mite pon NA):
p + xlim(c(4, 5)) + ylim(c(NA, 5)) #- cuidado, se pueden borrar observaciones

Con este enfoque tienes que tener cuidado, ya que si por ejemplo después utilizar alguna transformación estadística como por ejemplo geom_smooth(), las observaciones eliminadas al ajustar los límites no entrarÔn en el cÔlculo estadístico.

p + geom_smooth(color = "purple")
p + geom_smooth(color = "purple") + xlim(c(4, 5.7)) + ylim(c(1.5, 5))   # deletes points

  1. Cambiar los límites de los ejes X e Y haciendo un zoom en la región de interés pero sin eliminar puntos. Esto lo conseguimos con coord_cartesian().
p + geom_smooth(color = "purple")
p + geom_smooth(color = "purple") + coord_cartesian(xlim = c(4, 5.7), ylim = c(1.5, 5))


3.6 Escalas

Scales control the details of how data values are translated to visual properties. Override the default scales to tweak details like the axis labels or legend keys, or to use a completely different translation from data to aesthetic. labs() and lims() are convenient helpers for the most common adjustments to the labels and limits.

Las escalas permiten leer/interpretar un grÔfico; permiten interpretar los elementos geométricos (por ejemplo en nuestro grÔfico, los puntos) en función de los valores originales de las observaciones. Las escalas son un elemento mÔs de los grÔficos ggplot y se producen/controlan con la familia de funciones scale_xx()

En ggplot2 las escalas o guías se producen automÔticamente, no vemos que hagamos nada, pero under the hood se estÔn fijando con la familia de funciones scale_xx() que son las que controlan como se mapean los valores de las variables con las propiedades estéticas de nuestro grÔfico (por ejemplo el eje X) de forma que podamos interpretar la posición de los distintos puntos mirando las escalas. Las escalas también construyen los elementos que permiten leer/interpretar los grÔficos: los ejes y las leyendas.

Como ggplot2 hace el mapeo y genera las escalas y leyendas automƔticamente, en la prƔctica podemos hacer grƔficos sin saber como funcionan y, por tanto, sin saber manipular este elemento de un grƔfico ggplot. Pero si aprendemos a manipular las escalas, esto nos darƔ mƔs flexibilidad a la hora de utilizar ggplot2.

En muchos tipos de datos es importante pararse a pensar cual es la mejor escala para representar las variables. QuizƔs sea conveniente cambiar la escala de un eje para distribuir mejor las observaciones en el espacio, o para interpretar mejor las variaciones entre observaciones; por ejemplo la escala logarƭtmica o en porcentajes son a veces mƔs apropiadas que las escalas originales.

En realidad, para cada par variable/estƩtica representada en un grƔfico ggplot es necesaria una escala, y tendrƭa que fijarse con una de las funciones de la familia scale_xx(). Realmente cuando hacemos este grƔfico, en el que asociamos 3 variables a 3 propiedades estƩticas con aes(), se necesitarƭa especificar las escalas de las 3 variables:

p <- ggplot(iris, aes(Sepal.Length, Petal.Length)) +  geom_point(aes (color = Species)) 

Bien, pero entonces ¿por qué no lo hacemos?, ¿por qué no especificamos las escalas? Pues porque lo hace ggplot2 por nosotros. En realidad cuando ejecutamos la expresión anterior, realmente se estÔ haciendo lo siguiente:

ggplot(iris, aes(Sepal.Length, Petal.Length)) +   geom_point(aes (color = Species)) +
  scale_x_continuous() + 
  scale_y_continuous() + 
  scale_color_discrete()

Es decir, ggplot2 asigna a cada variable una escala, a las variables continuas les asigna una escala continua y a las categóricas una escala discreta. PERO, si queremos, si lo consideramos apropiado podríamos cambiar la escala. Por ejemplo:

p + scale_y_reverse() + scale_colour_grey()
p + scale_x_sqrt() + scale_y_log10()
p + scale_x_continuous(trans = "log")
p + scale_color_brewer(palette = "Dark2")

Solo represento la primera transformación, en la que se da la vuelta a la escala del eje Y y el color, asociado a la variable Species, pasa a escala de grises:


Labels de los ejes

TambiƩn se pueden modificar lo que vemos en los escalas. Antes vamos a modificar los tƭtulos de los ejes y leyendas.

p <- p + labs(x = "Eje X", y = "Eje Y", color = "Leyenda\n para el color")

De momento la escala del eje X varia aproximadamente de 3 a 8. Los tĆ­tulos de la escala del eje X sólo muestra los valores 5, 6, 7 y 8. Vamos a modificar ā€œel textoā€, los nĆŗmeros que se ven y que sirven de guĆ­a para la escala del eje X:

p + scale_x_continuous(breaks = seq(3, 10, 0.5), limits = c(3, 10)) 

En el eje Y se ven los nĆŗmeros 2, 4 y 6. Cambiemos los labels de su escala (no del eje Y, sino de la escala del eje Y):

p + scale_y_continuous(breaks = seq(0, 12, 0.25),  label = scales::dollar) 

TambiƩn se pueden modificar los de la leyenda para el color:

p + scale_color_manual(values = c("purple", "pink", "red2"), name = "Especies\n de lirios")

Si hubiese usado una variable discreta asociada a la estĆ©tica ā€œshapeā€, tendrĆ­amos que usar scale_shape_discrete(), si la variable asociada a shape fuese continua: scale_shape_continuous()

ĀæY si hubiĆ©semos usado una variable continua para la estĆ©tica ā€œfillā€? Pues scale_fill_continuous()


Familia de funciones de escala

• scale_continuous()
• scale
_discrete()
• scale_ordinal()
• scale
_manual()
• scale_{color/fill}brewer()
• scale
{color/fill}distiller()
• scale
{color/fill}_gradient()


3.7 Stats (transformaciones estadĆ­sticas)

Puede leerse en la web de ggplot2, concretamente aquĆ­ lo siguiente:

A layer combines data, aesthetic mapping, a geom (geometric object), a stat (statistical transformation), and a position adjustment. Typically, you will create layers using a geom_ function

PERO

A handful of layers are more easily specified with a stat_ function, drawing attention to the statistical transformation rather than the visual appearance. The computed variables can be mapped using stat().

Algunos grÔficos, como los grÔficos de puntos, no requieren del uso de transformaciones estadísticas de las observaciones, pero otros grÔficos como rectas o curvas de predicción o como los boxplots o diagramas de caja, sí que las necesitan.8 Podemos usar transformaciones estadísticas en grÔficos ggplot con la familia de funciones stat_xx()

Por ejemplo, cuando se hace un diagrama de caja o boxplot, no se representan las observaciones originales, sino que se muestran 5 estadĆ­sticos resumen de la distribución de los datos; es decir, se utiliza una transformación estadĆ­stica. Cuando usĆ”bamos geom_smoth() tampoco representĆ”bamos con Ć©l los datos originales, sino una transformación estadĆ­stica de estos. Concretamente la transformación estadĆ­stica que utiliza geom_smoth() es genĆ©ricamente un ā€œsmootherā€, calcula mediante una rolling-windows la media de y, condicionada a x.

Cada función geom_xx() que utilicemos, en realidad necesita de un stat_xx(), entonces ¿por qué nunca lo hemos usado/especificado nosotros? Con ggplot2 la razón es casi siempre la misma: porque under the hood ggplot2 hace muchas cosas por nosotros. En concreto, cada vez que usamos un geom_xx() en realidad ggplot2 estÔ fijando una transformación estadística por defecto por nosotros. Ggplot es un sistema muy completo, pero aún así, una vez lo entiendes, hacer grÔficos con el es relativamente fÔcil y rÔpido porque muchas de sus opciones opciones no hace falta especificarlas directamente.

Por ejemplo, ¿cual es la transformación estadística que se usa por defecto en geom_point()? Ninguna, bueno, en realidad usa stat = "identity", pero como lo especifica ggplot2 automÔticamente, nosotros no nos damos cuenta.

Cuando hacƭamos este grƔfico:

ggplot(iris, aes(Petal.Length, Sepal.Length)) + geom_point() 

En realidad estƔbamos haciendo

ggplot(iris, aes(Petal.Length, Sepal.Length)) + geom_point(stat = "identity")

¿Qué transformaciones estadísticas podemos hacer cuando usemos geom_point()? Por ejemplo, geom_point(stat = "unique") sólo representaría las observaciones únicas o NO repetidas. En este caso creo que iris no tiene observaciones repetidas, así si ejecutÔis la instrucción de abajo se seguirÔn visualizando los 150 lirios.

ggplot(iris, aes(Petal.Length, Sepal.Length)) + geom_point(stat = "unique") #- dejarĆ­a solo observaciones no repetidas

Podemos consultar las opciones por defecto completas de geom_point() aquĆ­

Cada geom_xx() tiene un default statistic, pero podemos cambiarlo y especificar otra stat para adaptarlo a nuestras necesidades. Por ejemplo, the default statistic for geom_bar() is stat_bin() pero podemos usar otras stat_xx. Otra vez parece un trabalenguas, pero cuando lo entiendes es relativamente sencillo.

Por ejemplo, en nuestro grÔfico de puntos, podemos usar otras transformaciones estadísticas, una de las que mÔs sentido tiene es calcular medias móviles con un método de alisado (smoother)

p <- ggplot(iris, aes(Petal.Length, Sepal.Length, color = Species)) 

p + geom_point(stat = "identity")

p + geom_point(stat = "smooth", method = "auto")

Aunque en este caso es bastante mƔs fƔcil hacerlo con:

p + geom_point() + geom_smooth()
p + geom_point() + stat_smooth()

p + geom_point() + stat_smooth(method = "lm",   se = FALSE,  size = 1)
p + geom_point() + geom_smooth(method = "lm",   se = FALSE,  size = 1)

p + geom_point() + geom_smooth(method = "lm", col = "#C42126",  se = FALSE,  size = 1)


Algunas transformaciones estadísticas útiles y en qué geoms estÔn disponibles:

- stat_bin(): geom_bar(), geom_freqpoly(), geom_histogram()  
- stat_bin2d(): geom_bin2d()  
- stat_bindot(): geom_dotplot()  
- stat_binhex(): geom_hex()   
- stat_boxplot(): geom_boxplot()    
- stat_contour(): geom_contour()  
- stat_quantile(): geom_quantile()   
- stat_smooth(): geom_smooth()  
- stat_sum(): geom_count()   

Es raro que tengamos que usar estas funciones stats_xx() directamente, pero si quieres ver que hacen exactamente conviene consultar la documentación para ver qué hace y cómo se aplica exactamente cada transformación estadística a los datos.

Hay otras funciones stat_xx() que no se pueden utilizar con las funciones geom_xx():

- stat_ecdf(): compute a empirical cumulative distribution plot.  
- stat_function(): compute y values from a function of x values.  
- stat_summary(): summarise y values at distinct x values.  
- stat_summary2d(), stat_summary_hex(): summarise binned values.  
- stat_qq(): perform calculations for a quantile-quantile plot.  
- stat_spoke(): convert angle and radius to position.  
- stat_unique(): remove duplicated rows.  


Algunos ejemplos

Veamos algunos ejemplos Ćŗtiles:

  1. En un diagrama de caja mostrar la media:

Queremos que la media se represente como un punto, entonces usamos geom_point() pero no queremos representar los valores originales, sino la media, así que dentro de geom usamos la opción stat = summary

ggplot(iris, aes(Species, Sepal.Length)) + 
  geom_boxplot() + 
  geom_point(stat = "summary", fun.y = "mean", colour = "red", size = 4)

Se conseguirĆ­a los mismo usando directamente la función stat_summary() con la opción geom = ā€œpointā€. SĆ­, en ggplot2 las cosas se pueden hacer de varias maneras!!

ggplot(iris, aes(Species, Sepal.Length)) + 
  geom_boxplot() + 
  stat_summary(geom = "point", fun.y = "mean", colour = "red", size = 4)
  1. Otro ejemplo: el default stat de geom_histogram es stat = ā€œbinā€, mostrandonos el nĆŗmero de observaciones en cada bin. Si queremos que nos muestre frecuencias relatĆ­vas al grupo o bin mĆ”s numeroso:
ggplot(iris, aes(Sepal.Length)) + geom_histogram() 

ggplot(iris, aes(Sepal.Length)) + geom_histogram(aes(y = stat(count / max(count))))


Bonus: Con stat_function() podemos dibujar curvas de densidad:

df <- tibble(x = c(-20, 20))
  
ggplot(df, aes(x = x)) +
stat_function(fun = dnorm, args = list(mean = 0, sd = 5), color = "black") +
stat_function(fun = dnorm, args = list(mean = 0, sd = 1), color = "red") +
stat_function(fun = dnorm, args = list(mean = 0, sd = 3), color = "blue")


3.8 Position adjustments

All layers have a position adjustment that resolves overlapping geoms. Override the default by using the position argument to the geom_ or stat_ function.

Los ajustes de posición afectan a la posición de los elementos de una capa. Los grĆ”ficos en los que mĆ”s se utilizan los ajustes de posición son los grĆ”ficos de barras. Su posición por defecto es position = ā€œstackā€. Se pueden cambiar con el argumento geom_bar(position = ā€œxxxxā€), aunque si usas las funcionesposition_xx()` tienes mĆ”s flexibilidad:

ggplot(iris , aes(Species)) + geom_bar()
ggplot(mtcars, aes(cyl)) +  geom_bar()

Para poder visualizar grƔficos de barras con 2 variables,tenemos que usar otro dataset: mtcars

ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar() #- pos
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar(position = "fill")
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar(position = "dodge")
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar(position = position_dodge2(preserve = "single"))


Por ejemplo, podemos modificar la posición por defecto de nuestro grÔfico de puntos con el iris dataset usando dos enfoques:

ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_point(position = "jitter", color = "pink") 
ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() + geom_jitter( color = "pink") 

geom_jitter(), o alternativamente geom_point(position = "jitter") cambia la posición original de los datos añadiendo un poco de ruido, haciendo que se desplacen un poco. Esta técnica se usa muchos cuando hay muchos datos similares (overplotting).


3.9 Coordenadas

The coordinate system determines how the x and y aesthetics combine to position elements in the plot. The default coordinate system is Cartesian (coord_cartesian()), which can be tweaked with coord_map(), coord_fixed(), coord_flip(), and coord_trans(), or completely replaced with coord_polar()

Por ejemplo: coord_fixed(). En nuestro grƔfico, tanto la longitud del pƩtalo como del sƩpalo se miden en las mismas unidades, asƭ que su ratio implƭcito es 1 a 1. Cambiemos el ratio de las coordenadas con coord_fixed()

p <- ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point() 

p + coord_fixed(ratio = 1/3)
p + coord_fixed(ratio = 3/1)


4. Combinando grƔficos

La técnica del faceting es una fantÔstica herramienta para dividir un grÔfico en varios, en principio en función de una variable categórica, si queremos usar una variable continua primero habría que discretizarla. PERO, a veces lo que interesa es crear una figura compuesta de varios grÔficos diferentes. Esto no es un elemento mÔs de ggplot2 es una operación que hacemos sobre un grupo de varios grÔficos.

Podemos hacerlo con varios paquetes:

  • con el paquete gridExtra y su función grid.arrange()
library(gridExtra)
p1 <- ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + geom_point()
p2 <- ggplot(iris)+ aes(Species, Sepal.Length) + geom_boxplot()

grid.arrange(p1, p2, ncol = 2, widths = c(6.5, 3.5))

AdemƔs de los argumentos ncol, nrow y widths, con gridExtra se pueden hacer composiciones mƔs complejas

library(patchwork)
p1 + p2 + plot_layout(ncol = 2)

  • con el paquete cowplot. Este paquete tambiĆ©n se puede utilizar para hacer anotaciones o para incorporar imĆ”genes a nuestros grĆ”ficos
library(cowplot)
ggdraw(p1) + draw_image(here::here("./imagenes/Captura.JPG"), 
               x = 1, y = 1, hjust = 1, vjust = 1, width = 0.33, height = 0.42)


5. Exportando grƔficos

La mayorƭa de las veces, cuando creamos un grƔfico lo vemos inmediatamente, pero tambiƩn podemos asignarle un nombre y manipularlo mƔs adelante.

p <- ggplot(iris, aes(Sepal.Length, Petal.Length)) + geom_point()
p + geom_line()

Una vez que tienes el grƔfico guardado como un objeto en el R environment puedes hacer varias cosas con Ʃl:

  1. verlo en la pantalla con print() o llamĆ”ndolo con ā€œsu nombreā€.

  2. guardarlo en un fichero en diferentes formatos. Piensa que estamos guardando el grÔfico, la imagen, la representación del grÔfico, no el objeto R.

    • Podemos usar la ā€œExport Tabā€ en el Plot Pane de Rstudio. Lo grabarĆ” en baja resolución. TambiĆ©n podemos cambiar las dimensiones (anchura y altura) del grĆ”fico. Si vamos a utilizar el grĆ”fico en Word es conveniente guardar el grĆ”fico como Metafile.

    • Como alternativa podemos usar la función ggsave() que, ademas, nos permite cambiar el tamaƱo y la resolución del grĆ”fico con los argumentos width, height y dpi. Nota: Los parĆ”metros width and height tambiĆ©n determinan el tamaƱo de la fuente del grĆ”fico guardado.

ggsave("./graf_out/my_grafico_chulo.png", p, width = 15, height = 10)
ggsave("filename.png", plot = my_plot, width = 8, height = 6, units = "in", dpi = "retina")

# Tb funciona para figuras compuestas de varios grƔficos
grafico_combinado <- grid.arrange(p1, p2, ncol = 2, widths = c(6, 4))
ggsave("fig_output/my_combo_plot.png", grafico_combinado, width = 10, dpi = 300)
  • Finalmente, podemos guardar una copia completa del grĆ”fico, no de su representación visual, sino del objeto R con saveRDS(), para luego leerlo con readRDS(). Aunque, ya que nos ponemos, es mejor guardar el script que genera el objeto. Sólo tendrĆ­a sentido si fuese muy costoso, por ejemplo en tĆ©rminos de tiempo, reproducir el grĆ”fico.
saveRDS(p, "plot.rds")
my_valioso_grafico <- readRDS("plot.rds")



6. Tipos de grƔficos

En estÔ sección presentaremos algunos ejemplos de algunos de los grÔficos mÔs utilizados en el anÔlisis de datos. Puedes ver listados mÔs completos en:


6.1 Histogramas

Se utilizan para mostrar la distribución de UNA variable continua, por ejemplo la longitud del sépalo (Sepal.Length).

Para hacer histogramas en ggplot2 se utiliza geom_histogram y, a veces, geom_freqpoly(). Los dos geoms trabajan de la misma manera, dividen la variable x en intervalos y cuentan las observaciones en cada intervalo, para mostrarlas en el eje Y. La diferencia entre ellos es que geom_histogram utiliza barras para mostrar el nĆŗmero de observaciones o frecuencia absoluta, mientras que geom_freqpoly() usa lineas.

ggplot(iris, aes(Sepal.Length)) + geom_histogram() 

Como ves, lo que hace un histograma es dividir el eje X en intervalos o ā€œbinsā€ y mostrar en el eje Y el nĆŗmero de observaciones de x que caen en cada ā€œbinā€. AdemĆ”s, ggplot2 nos avisa de que por defecto se divide el eje X en 30 intervalos, pero que podemos cambiarlo con bins = my_n_de_bins.

p <- ggplot(iris, aes(Sepal.Length))
p + geom_histogram(bins = 40) + xlab("40 intervalos")
p + geom_histogram(bins = 4) + xlab("Sólo 4 intervalos")

Otra opción interesante es elegir la anchura del intervalo con la opción binwidth.

p <- ggplot(iris, aes(Sepal.Length))
p + geom_histogram(binwidth = 0.1) + xlab("He elegido la anchura = 0.1")
p + geom_histogram(binwidth = 1.1) + xlab("Esta vez la anchura = 1.1")

Si en el eje Y queremos frecuencias relativas o porcentajes en lugar de counts, podemos hacerlo con:

ggplot(iris, aes(Sepal.Length)) + 
  geom_histogram(aes(y = stat(count) / sum(count)), bins = 10) +
  scale_y_continuous(labels = scales::percent)

Se puede mejorar bastante la apariencia del histograma jugando con los colores y opciones:

ggplot(iris, aes(Sepal.Length)) + geom_histogram(bins = 10, color = "black", fill = "tomato")

Si asociamos/mapeamos la variable Species a alguna estĆ©tica, por ejemplo a la estĆ©tica ā€œfillā€ o relleno de las barras con aes(fill = Species), se visualizarĆ” quĆ© parte de cada intervalo pertenece a cada clase de lirio.

ggplot(iris, aes(Sepal.Length)) + geom_histogram(bins = 10, aes(fill = Species), color = "black")

Evidentemente, también podemos visualizar como cambia la distribución de la anchura del sépalo entre los 3 tipos de lirios si hacemos un small multiple:

p <- ggplot(iris, aes(Sepal.Length, fill = Species)) + geom_histogram(bins = 10, color = "black")
p + facet_grid(cols = vars(Species))

Para mejorar la visualización podemos poner en el fondo el histograma para todos los datos. Lo aprendí aquí, aunque ahora mismo hay un paquete para implementar esta técnica, es el paquete gghighlight.

iris_backgroung <- iris %>% select(-Species)
ggplot(iris, aes(x = Sepal.Length)) +
  geom_histogram(data = iris_backgroung, fill = "grey", bins = 15) +
  geom_histogram(aes(fill = Species), bins = 15) +
  facet_grid(cols = vars(Species))


geom_density()

Una alternativa a los histogramas son los grÔficos de densidad con geom_density(). Pero, según Hadley:

they are harder to interpret since the underlying computations are more complex. They also make assumptions that are not true for all data, namely that the underlying distribution is continuous, unbounded, and smooth.

Como dice Hadley, geom_density() estima la función de densidad, por lo que la estimación depende de una serie de paramétros como adjust. Hacemos uso de xlim(3, 9) para expandir loslímites del eje X.

ggplot(iris, aes(Sepal.Length)) + 
  geom_density(color = "red",   size = 1.2) +  
  geom_density(color = "blue",  size = 1.2, adjust = 3) +
  geom_density(color = "black", size = 1.2, adjust = 0.5) +  xlim(2, 10)


Podemos asociar la variable Species a la estética fill con aes(fill = Species) para obtener una estimación de la densidad para cada tipo de lirio.

ggplot(iris, aes(Sepal.Length, fill = Species)) + geom_density(position = "stack", alpha = 0.5)


Hay muchas otras posibilidades. Por ejemplo:

ggplot(iris, aes(Sepal.Length, fill = Species)) + geom_density(position = "fill", alpha = 0.5) #- position = "fill"

ggplot(iris, aes(Sepal.Length, stat(count), fill = Species)) + geom_density(position = "stack", alpha = 0.5) #- stat(count)

Muchas veces se suelen hacer los histogramas superponiendo la f. de densidad normal o una estimación de la densidad de x con geom_density() o con geom_line(stat="density"). Para que el grÔfico se vea mejor, ajustaremos el eje X con xlim():

#- calculamos media y desviación tipica de Sepal.Length para luego usarlas para construir la curva normal
media <- mean(iris$Sepal.Length, na.rm = TRUE)     #- media de la longitud del sƩpalo
desviacion <- sd(iris$Sepal.Length, na.rm = TRUE)  #- desviación 

#- hacemos el histograma
p <- ggplot(iris, aes(Sepal.Length)) +
      geom_histogram(aes(y=..density..),  color="black", fill = "steelblue", alpha = 0.2)

#- le aƱadimos la densiidad estimada y la normal
p + geom_density( color="purple", size = 1) +
    stat_function(fun = dnorm, colour = "red", size = 1, args = list(mean = media, sd = desviacion))  + 
    xlim(c(min(iris$Sepal.Length)-1, 9))

Si necesitas saber mƔs cosas sobre los histogramas puedes acudir aquƭ o aquƭ.

Joy Division plots

Hace poco apareció el paquete ggridges y se pusieron de moda estos tipos de grÔficos:

library(ggridges)
ggplot(iris, aes(x = Sepal.Length, y = Species)) + geom_density_ridges(aes(fill = Species), alpha = 0.5)

Como se parecen a la mĆ­tica portada del primer disco de Joy Division, algunos los conocen como Joy Division plots. Abajo os pongo un ejemplo sacado de la vignette del paquete ggridges.

library(viridis)
ggplot(lincoln_weather, aes(x = `Mean Temperature [F]`, y = `Month`, fill = ..x..)) +
  geom_density_ridges_gradient(scale = 3, rel_min_height = 0.01) +
  scale_fill_viridis(name = "Temp. [F]", option = "C") +
  labs(title = 'Temperatures in Lincoln NE in 2016')


6.2 Scatter plot

Scatter plot, grÔfico de puntos o grÔfico X-Y. Se utiliza para mostrar la relación entre DOS variables continuas. Lo tenemos mÔs que visto, ya que es el tipo de grÔfico que hemos utilizado durante todo el tutorial.

ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) + 
     geom_point() +
     labs(title = "GrƔfico 1: Longitud del sƩpalo frente al pƩtalo",
       subtitle = "(diferenciando por especie de lirio)",
       caption = "Datos provenientes del Iris dataset",
       x = "Longitud del sƩpalo",
       y = "Longitud del pƩtalo",
       color = "Especie de lirio")

Overplotting

Los grĆ”ficos de puntos se usan habitualmente para mostrar la relación entre dos variables continuas. En conjuntos de datos con muchas observaciones pueden tener un problema de ā€œoverplottingā€. Esta situación ocurre cuando unos puntos se dibujan encima de otros, de forma que no se aprecia bien la relación entre las variables

Hay varias formas de tratar este problema. VeƔmoslo con un ejemplo sacado de la web de ggplot2:

set.seed(1234)
df <- data.frame(x = rnorm(2000), y = rnorm(2000))   #- creamos un conjuto de datos con 2000 observaciones y 2 v.

p <- ggplot(df, aes(x, y)) + xlab(NULL) + ylab(NULL)

p + geom_point()
p + geom_point(shape = 1) # Hollow circles
p + geom_point(shape = ".") # Pixel sized

#- For larger datasets with more overplotting, you can use alpha blending (transparency) t
p + geom_point(alpha = 1 / 10)


De forma alternativa, podemos lidiar con el overplotting utilizando otro enfoque que consiste en pensar en el overplotting como un problema de densidad en 2 dimensiones y utilizar un hex-plot. En un hex-plot se representan regiones (generalmente hexagonales) coloreadas en función del número de observaciones que caen en cada región, de forma que es equivalente a un histograma pero en 2d.

p + geom_bin2d()
p + geom_bin2d(bins = 10)
p + geom_hex()
p + geom_hex() + scale_fill_gradient2(low = "#132B43", high = "#56B1F7")  

En este post utilizan hex-plots para mostrar las zonas desde donde tiran a canasta los jugadores NBA. En este otro post utilizan una combinación de geom_point(alpha = .2, pch = 15) + geom_density_2d() para analizar las puntuaciones de videojuegos. AdemÔs hace un uso avanzado de las escalas, al menos para mi, para que el grÔfico sea fÔcil de visualizar.

Acaba de aparecer un nuevo geom: geom_pointdensity(). Como dicen en su repo de Github: ā€œA cross between a scatter plot and a 2D density plotā€.

#devtools::install_github("LKremer/ggpointdensity")
library(viridis)
library(ggpointdensity)
p +  geom_pointdensity(adjust = 7) +
  scale_color_viridis()

Si quieres saber mÔs cosas sobre cómo hacer scatterplots con ggplot2 puedes hacerlo por ejemplo, aquí o aquí.


6.3 Boxplot (grƔficos de caja)

Para visualizar una variable cuantitativa se suelen usar los histogramas, pero si lo que quieres es visualizar como varían los valores de una variable continua en función de los valores de una variable categórica (Species) se suelen usan boxplots o diagramas de caja. Son útiles para comparar diferentes grupos y para identificar outliers.

Para hacer un boxplot o diagrama de caja se utiliza geom_boxplot(). El grƔfico muestra 5 estadƭsticos: la mediana con una linea gruesa, el primer y tercer cuartil de los datos con los limites de la caja y el mƔximo y el mƭnimo (los limites de la linea vertical). Adicionalmente, si existen outliers, estos tambiƩn se representarƔn.

ggplot(iris, aes(x = Species,  y = Sepal.Length)) + geom_boxplot() 

Se pueden cambiar algunas opciones del grƔfico

p <- ggplot(iris, aes(x = Species,  y = Sepal.Length)) 
p + geom_boxplot(aes(fill = Species), outlier.colour = "purple")

A veces, para mejorar la visualización, conviene rotar los ejes. Podemos hacerlo con coord_flip()

p <- ggplot(iris, aes(x = Species,  y = Sepal.Length)) + geom_boxplot(aes(fill = Species)) 
p + coord_flip()

Para mejorar la visualización podemos incluir las observaciones originales con geom_point(), pero habría mucho overplotting, así que mejor con geom_jitter()

p + geom_jitter(width = 0.15, alpha = 1/4, color = "tomato")

Podemos añadir con stats() algún estadístico mÔs con stats_xx(), por ejemplo la media con stats_summary:

p + stat_summary(fun.y = "mean", geom = "point", color = "purple", size = 2.5)

Los boxplots resumen la distribución de una variable cuantitativa con sólo cinco números, proporcionando un resumen útil de los datos, pero ocultan la forma de la distribución; por ejemplo, si la distribución fuese bimodal, no lo apreciaríamos. AdemÔs, aunque usemos geom_jitter() para superponer los valores originales, a veces es difícil a simple vista inferir la distribución de estos. Por ello hay varias técnicas/geoms útiles que ayudan a resolver el problema, por ejemplo geom_violin(). Una alternativa al boxplot son los grÔficos de violín donde se estima y muestra la forma de la distribución de las observaciones:

p <- ggplot(iris, aes(x = Species,  y = Sepal.Length)) 
p + geom_violin(aes(fill = Species), alpha = 0.6)
p + geom_violin(aes(fill = Species), alpha = 0.6) + geom_jitter(width = 0.15, alpha = 1/4)


En el ejemplo que hemos usado hemos tenido suerte y los tres grupos estaban ordenados, pero otras veces no tendremos tanta suerte, por ejemplo si en lugar de graficar la longitud de sƩpalo queremos visualizar la anchura (Sepal.Width):

ggplot(iris, aes(x = Species,  y = Sepal.Width)) + geom_boxplot()

¿Cómo ordenamos los grupos? Por ejemplo podemos ordenarlos de menor a mayor en función de su anchura media del sépalo. Para ello usamos stats::reorder()

ggplot(iris, aes(x = reorder(Species, Sepal.Width, mean),  y = Sepal.Width)) + geom_boxplot() +
  xlab("De menor a mayor anchura del sƩpalo")

6.4 GrƔficos de barras

Los grÔficos de barras se utilizan para representar una variable categórica, como por ejemplo Species, o variables cuantitativas discretas. Se representan barras verticales proporcionales a los valores de la variable en cada categoría o valor. Para crear grÔficos de barras con ggplot2 se usa geom_bar().

Para hacer nuestro primer grÔfico de barras no vamos a utilizar iris porque en la única variable categórica (Species) resulta que hay 50 lirios en cada tipo de especie. Usaremos el data.frame mpg del paquete ggplot2 que contiene 234 observaciones sobre distintos modelos de coches y sus características. Algunos de los ejemplos estÔn sacados de la web de ggplot2.

p <- ggplot(mpg, aes(class))
p + geom_bar()
p + geom_bar(fill = "steelblue") + coord_flip()

Como vemos, la variable class es categórica, concretamente tiene 7 grupos o categorías de coches. Las barras verticales son proporcionales al número de vehículos en cada categoría. Con coord_flip() las categorías pasan a representarse en el eje Y, haciendo que, generalmente, se visualicen mejor los nombres de las categorías.


Si en lugar de tener una tabla de datos, tenemos ya una tabla de frecuencias, tendremos que especificar en aes() que la variable con las frecuencias se mapee/asocie al eje Y: aes(x = variable, y = frecuencias). AdemƔs tendrƔs que usar geom_bar(stat = "identity") o directamente usar geom_col() que ya usa por defecto stat_identity(). VeƔmoslo con un ejemplo:

df <- mpg %>% group_by(class) %>% count
p <- ggplot(df, aes(x = class, y = n))
p + geom_bar(stat = "identity",  fill = "steelblue") 
# p + geom_col(fill = "steelblue")                          #- hace exactamente el mismo plot


Distintas posiciones para las barras

Para esta subsección utilizaremos el data.frame mtcars

ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar() #- pos
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar(position = "fill")
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar(position = "dodge")
ggplot(mtcars, aes(factor(cyl), fill = factor(vs))) + geom_bar(position = position_dodge2(preserve = "single"))


Reordenando las categorĆ­as

A veces es importante reordenar las barras para mejorar la visualización. Para ello tendremos que usar factores. Por ejemplo, volviendo al mpg dataset:

df <- mpg
df <- df %>% mutate(class = forcats::as_factor(class)) #- convertimos la v. class a factor con la f. as_factor()
df <- df %>% mutate(class = forcats::fct_infreq(class)) #- fct_infreq() los niveles del factor segĆŗn su frecuencia de mayor a menor
p <- ggplot(df, aes(fct_rev(class))) #- fct_rev() ordena los levels de menor a mayor
p + geom_bar(fill = "steelblue") + coord_flip()


Si queremos que en las barras se visualice el nĆŗmero de observaciones o frecuencia absoluta de cada categorĆ­a:

p + geom_bar(fill = "steelblue") + coord_flip() +
    geom_text(stat='count', aes(label = ..count.. ), hjust = -0.15, size = 3.25) 


Porcentajes en lugar de counts

Si queremos que las barras representen porcentajes en lugar de numero de casos en cada categorĆ­a:

p <- ggplot(df, aes(fct_rev(class))) #- fct_rev() ordena los levels de menor a mayor
p + geom_bar(aes( y = (..count..)/sum(..count..)), fill = "steelblue")  + coord_flip()

En realidad esto mismo se pude hacer de varias formas. En este post nos muestran 5 formas de hacerlo.


Si quieres saber mƔs sobre como hacer grƔficos de barras con ggplot2, puedes hacerlo aquƭ o aquƭ.


6.5 GrƔficos de lineas

Los grÔficos de lineas se usan principalmente para mostrar la evolución de variables en el tiempo. Generalmente la variable que se muestra en el eje X es el tiempo. En Economía estos grÔficos puede que sean los mÔs usados y habitualmente nos referimos a ellos como grÔficos temporales.

Podemos simular grƔficos de tiempo con geom_line() y geom_path(). Estos 2 geoms grafican lineas entre dos observaciones de una variable. Por ejemplo:

ggplot(economics, aes(date, uempmed)) + geom_line()

Empecemos usando el conjunto de datos gapminder para mostrar las observaciones de la variable lifeExp para EspaƱa:

library(gapminder)
gapminder %>% filter(country == "Spain") %>% 
ggplot(aes(x = year, y = lifeExp)) + geom_line() + geom_point()

Visualicemos ahora 4 series de tiempo, la esperanza de vida en cuatro paƭses europeos. Para poder distinguir las lineas de cada paƭs podemos asociar la variable country a la caracterƭstica estƩtica color: aes(color = country)

gapminder %>% filter(country %in% c("Spain", "France", "Norway", "Belgium")) %>% 
ggplot(aes(x = year, y = lifeExp, color = country)) + geom_line() + geom_point()

Una tonterĆ­a pero que puede ser de utilidad para saber cuanta diferencia hay al final del plot. Lo aprendĆ­ aquĆ­

df <- gapminder %>% filter(country %in% c("Spain", "France", "Norway", "Belgium")) 
lifeExp_ends <- df %>% group_by(country) %>% top_n(1, year) %>% pull(lifeExp) #- vector con los valores Ćŗltimos de lifeExp
ggplot(df, aes(x = year, y = lifeExp, color = country)) + geom_line() + 
     scale_y_continuous(sec.axis = sec_axis(~ ., breaks = lifeExp_ends)) +   #- sec_axis() especifica un eje secundario
     scale_x_continuous(expand = c(0, 0))


El siguiente ejemplo estÔ sacado de este libro aún en construcción. Utiliza el paquete tidyquant para descargar las cotizaciones de las 4 principales empresas tecnológicas, las GAFA. tidyquant tiene muchas funciones interesantes, puedes verlas corriendo tq_transmute_fun_options().

library(tidyquant)
stocks <- c("GOOGL","AMZN","FB","AAPL") #- seleccionamos a las GAFAs
df <- tq_get(stocks, from = as.Date("2013-01-01"), to = as.Date("2013-12-31"))

ggplot(df, aes(date, y = close, color = fct_reorder2(symbol, date, close))) +
  geom_line() + xlab("") + ylab("") +
  theme(legend.title = element_blank())

Reescalamos para que las series comiencen todas en 100

df <- df %>% group_by(symbol) %>% mutate(rescaled_close = 100*close / close[1])

ggplot(df, aes(date, y = rescaled_close, color = fct_reorder2(symbol, date, rescaled_close))) +
  geom_line() + xlab("") + ylab("") +
  theme(legend.title = element_blank())


Otro paquete muy Ćŗtil para analizar y descargar datos financieros es quantmod. El ejemplo siguiente lo he sacado de este fantastico libro.

library(quantmod)
today <- Sys.Date()
three_months_ago <- seq(today, length = 2, by = "-3 months")[2]
getSymbols("AAPL", from = three_months_ago, to = today)
#> [1] "AAPL"
candleChart(AAPL, theme = 'white', type = 'candles')


Si quieres saber mƔs sobre grƔficos de lineas puedes ir aquƭ.

Un poco sobre datos temporales

Para trabajar con datos temporales, R tiene distintos paquetes y estructuras, pero para manipular fechas y datos temporales en el tidyverse tenemos el paquete lubridate. Para una introducción a fechas y tiempo en R aquí.

En general, para realizar anĆ”lisis estadĆ­sticos en R con series de tiempo se necesita que los datos estĆ©n en matrices, pero nosotros en el curso estamos trabajando con el tidyverse, y la la estructura de datos usada son los dataframes o tibbles, esto era un problema, pero recientemente, el paquete tsibble ha extendido el tidyverse y las tibbles a los datos temporales, creando una nueva estructura de datos: las ā€œtsibblesā€.

ApoyĆ”ndose en esta nueva estructura de datos, las ā€œtsibblesā€, el paquete feast proporciona las herramientas y funciones necesarias para trabajar con series temporales en un entorno tidy. feast es un acrónimo de Feature Extraction And Statistics for Time Series. Para una introducción a feast puedes ir aquĆ­ o aquĆ­.

Asimismo, el paquete fable tambiĆ©n utiliza ā€œtsibblesā€ y proporciona herramientas para la predicción de series temporales, incluyendo modelos ARIMA en el entorno tidyverse. Para una introducción a fable puedes ir aquĆ­.

AquĆ­ puedes ver una conferencia en la que se explica las principales ideas de esta nueva forma de trabajar con series temporales en el tidyverse


7. MƔs detalles/cosas

7.1 geom_smooth()

Ya hemos usado geom_smooth(). Un argumento importante de geom_smooth() es method con el que se selecciona el mĆ©todo con el que se obtiene la smooth curve. El mĆ©todo por defecto es method = "loess". Puedes controlar el nivel de smoothing con el parametro ā€œspanā€, que va de 0 to 1 (mayor suavizado).

Otras opciones para geom_smooth():

  • method = ā€œlmā€ fits a linear model, giving the line of best fit.

  • method = ā€œrlmā€ works like lm(), but uses a robust fitting algorithm so that outliers don’t affect the fit as much. It’s part of the MASS package, so remember to load that first.

  • stat_smooth(se = FALSE) , indica si se representan intervalos de confianza para la linea suavizada.

AdemĆ”s de los mĆ©todos implementados, podemos elegir nuestro propio mĆ©todo, ya sea usando el argumento ā€œformulaā€ o definiendo nuestro propio mĆ©todo de alisado como nos cuentan aquĆ­

p <- ggplot(iris, aes(Sepal.Length, Petal.Length)) + 
     geom_point() 
p + geom_smooth(method = "lm", formula = y ~ poly(x,4))

Podemos, obviamente, comparar dos mƩtodos de alisado. Para poner nombre a los diferentes mƩtodos se puede hacer lo siguiente:

library(tidyverse)
p <- ggplot(iris, aes(Sepal.Length, Petal.Length)) + 
     geom_point()
p + geom_smooth(aes(color = "loess") , method = "loess", se = FALSE) + 
    geom_smooth(aes(color = "lm")    , method = "lm"   , se = FALSE)


7.2 Ɓreas bajo la curva

Para un profesor de estadƭstica/econometrƭa es importante saber hacer grƔficos como estos:

library(tidyverse) ggplot(xx, aes(x)) + geom_function(fun = function(x) {x**3} , colour = ā€œredā€)

ggplot(xx, aes(x)) + geom_function(fun = ~ .x**3 , colour = ā€œredā€)

xx <- data.frame(x = c(-2, 2))

ggplot(xx, aes(x)) + geom_function(fun = function(x) {x**3} ,  colour = "red")

ggplot(xx, aes(x)) + geom_function(fun = ~ .x**3 ,  colour = "red")

ggplot(xx, aes(x)) + stat_function(fun = function(x) { x**3 }, geom = "line")

ggplot(xx, aes(x)) + geom_function(fun = dnorm)  

ggplot(xx, aes(x = x))+
  geom_function(fun = dnorm, xlim = c(-4, 0)) +
  stat_function(fun = dnorm, geom = "area", fill = "steelblue", xlim = c(0, 4)) +
  xlim(-5, 5)

Lo aprendĆ­ aquĆ­.

Inspirado por Fran Morillas, otro ejemplo:

nn <- 300 ; esperanza <- 10; sd <- 6
datos <- data.frame(normal = rnorm(nn, esperanza, sd))

ggplot(datos, aes(normal)) +
    geom_histogram(aes(y = after_stat(density)), bins = 30) +
    geom_density(color = "blue") +
    geom_function(fun = dnorm, args = list(esperanza, sd), colour = "red")

Aunque, la verdad, hoy en dia hay paquetes en R para todo. Por ejemplo el paquete nhstplot

library(nhstplot)
plotttest(t = 1.96, df = 1000 , tails = "one")


7.3 Labelling las observaciones con ggrepel

El grÔfico de abajo se basa en este post de Julia Silge. Utiliza el paquete `ggrepel para poder ver a quien pertenece la observación en un grÔfico de puntos.

library(ggrepel)
df <- gapminder::gapminder %>% filter(year == "2007")  %>% filter(continent == "Europe")
ggplot(df, aes(gdpPercap, lifeExp, label = country)) + geom_point() +
     labs(title = "GrƔfico 1: Esperanza de vida frente a PIB per cƔpita" ,
       caption = "Datos provenientes de gapminder",
       y = "lifeExp",
       x = "gdpPercap") + geom_smooth() +
        geom_label_repel() 


7.4 GGally package

Hay muchos paquetes que proporcionan formas rƔpidas de hacer grƔficos como por ejemplo este del paquete GGally:

library(GGally)
ggpairs(iris)
ggpairs(iris %>% select(1:4) %>% na.omit(), progress = FALSE, lower = list(combo = wrap("facethist", bins=6)))


7.5 Un poco mƔs de anotaciones

Para que un grÔfico sea efectivo y nos haga ver algún hecho o característica de los datos, muchas veces es preciso hacer anotaciones en el grÔfico para señalar o resaltar ciertos aspectos de este. En esta subsección presento algún ejemplo de uso de las anotaciones en grÔficos ggplot.

  • En primer lugar un ejemplo de Hadley. Hadley nos dice que no hay nada novedoso excepto el uso de -Inf and Inf como posiciones para referirse a los lĆ­mites del grĆ”fico, the top and bottom (or left and right) limits of the plot.
presidential <- subset(presidential, start > economics$date[1])

p <- ggplot(economics) +  geom_line(aes(date, unemploy)) + 
  scale_fill_manual(values = c("blue", "red")) +
  xlab("date") + 
  ylab("unemployment")

#- comienzan las anotaciones  
p + geom_rect(aes(xmin = start, xmax = end, fill = party), 
              ymin = -Inf, ymax = Inf, alpha = 0.2, data = presidential) + 
    geom_vline(aes(xintercept = as.numeric(start)), 
               data = presidential, colour = "grey50", alpha = 0.5) + 
    geom_text(aes(x = start, y = 2500, label = name), 
               data = presidential, size = 3, vjust = 0, hjust = 0, nudge_x = 50) 

Una forma común de anotación consiste en marcar o subrayar un conjunto de puntos. Por ejemplo marcar los coches de la marca Subaru en el siguiente grÔfico. Se puede hacer de la siguiente manera:

p <- ggplot(mpg, aes(displ, hwy)) +
  geom_point(data = filter(mpg, manufacturer == "subaru"), colour = "orange", size = 3) +
  geom_point() 
p

Como ves, lo que se ha hecho es superponer una capa con solo las observaciones de Subaru y graficarlas con un punto mƔs grande de lo habitual y con un color llamativo. El problema es que se ve que sean las observaciones de Subaru. Esto se puede resolver de varias maneras usando annotate():

p + annotate(geom = "point", x = 5.5, y = 40, colour = "orange", size = 3) + 
    annotate(geom = "point", x = 5.5, y = 40) + 
    annotate(geom = "text", x = 5.6, y = 40, label = "subaru", hjust = "left")

O de esta otra forma:

p + annotate(geom = "curve", x = 4, y = 35, xend = 2.65, yend = 27, 
              curvature = .3, arrow = arrow(length = unit(2, "mm"))) +
   annotate(geom = "text", x = 4.1, y = 35, label = "subaru", hjust = "left")

El paquete ggforce es una extensión a ggplot2 que puede servir para muchas cosas, entre ellas hacer anotaciones o marcas en grÔficos ggplot. Por ejemplo:

ggplot(iris, aes(Sepal.Length, Petal.Length)) +
  geom_point(aes(color = Species)) + 
  ggforce::geom_mark_ellipse(aes(label = Species, group = Species))

Con ggforce podemos hasta simular un zoom:

ggplot(iris, aes(Petal.Length, Petal.Width, colour = Species)) +
  geom_point() +
  ggforce::facet_zoom(x = Species == "versicolor")


El paquete/extensión ggforce es pretty awesome. En este post puede verlo en acción haciendo mapas.


Otro enfoque para hacer anotaciones, bueno, en realidad centrase en un grupo de observaciones es utilizar el paquete gghighlight:

df <- gapminder::gapminder %>% filter(continent == "Europe")
ggplot(df, aes(year, lifeExp, group = country)) + 
  geom_line() + 
  geom_point() + 
  gghighlight::gghighlight(country %in% c("Spain", "Portugal"))

ggplot(iris, aes(Sepal.Length, Petal.Length, color = as.factor(Species))) +
  geom_point() + 
  gghighlight::gghighlight() + 
  facet_wrap(vars(Species))


8. Asistentes para ggplot2

Ahora mismo existen 2 asistentes para crear grƔficos ggplot a travƩs de interfaces grƔficas: ggThemeAssist y esquisse

  • El paquete ggThemeAssist facilita mediante un adding de RStudio la edición de los detalles de un grĆ”fico; es decir, puedes comenzar haciendo un grĆ”fico bĆ”sico en RStudio, para despuĆ©s abrir la interfaz de ggThemeAssist para modificar con el asistente visual todos los elementos estĆ©ticos de grĆ”fico como tĆ­tulos, leyendas, colores etc… etc…

    Para ello, primero has de instalar el paquete con install.packages("ggThemeAssist"), despuĆ©s cargarlo con library(ggThemeAssist). Una vez has cargado el paquete en memoria has de seleccionar con el cursor el código que genera el ggplot que quieres modificar/tunear. Una vez tienes marcado con el ratón el código del grĆ”fico, has de seguir, en RStudio, esta ruta de menĆŗs: Tools > Addings > Browse Addings ..., para seleccionar finalmente el adding llamado ā€œggplot Theme Asistantā€.

    Se abrirÔ un interfaz donde podrÔs modificar la mayoría de elementos del grÔfico. Cuando hayas dejado el grÔfico a tu gusto pinchas en DONE y te devolverÔ el código que reproduce el grÔfico tal y como lo elegiste en el interfaz.

    PodƩis ver un ejemplo en https://github.com/calligross/ggthemeassist


  • El paquete esquisse permite crear grĆ”ficos ggplot desde cero con una interfaz grĆ”fica.

    Para usarlo tienes que instalarlo con install.packages("esquisse") para luego abrirlo siguiendo esta ruta de menĆŗs en RStudio: Tools > Addings > Browse Addings ... para elegir el adding con nombre: ā€œesquisse - ggplot builderā€.


9. GrƔficos interactivos

Los grĆ”ficos interactivos, como su nombre indica, permite al usuario interactuar con el grĆ”fico, abriĆ©ndose posibilidades como centrarse en parte del grĆ”fico (zooming), highlighting, o mostrar información adicional al pinchar en algĆŗn elemento del grĆ”fico, etc …

En general, JavaScript (JS) es el lenguaje utilizado para hacer grĆ”ficos interactivos con librerĆ­as como D3, Chart, Plotly, Vis Highcharts, …

Recientemente, el paquete de R htmlwidgets ha facilitado el uso de las librerƭas de JS en R. Actualmente, paquetes de R, como leaflet, DT, dygraphs, networkD3 y muchos otros, utilizan el framework propuesto por htmlwidgets para hacer disponibles los grƔficos interactivos de JS en R.

Para daros cuenta de lo fƔcil que es hacer un grƔfico interactivo con R usaremos el paquete plotly que hace posible usar la librerƭa plotly.js en R. Con plotly se pueden hacer muchos tipos de grƔficos, pero por ejemplo, permite con una sola linea convertir un grƔfico ggplot en interactivo:

library(plotly)
p <- ggplot(iris, aes(Sepal.Length, Petal.Length, color = Species)) +  geom_point() + geom_smooth()
ggplotly(p)

AquĆ­ puedes encontraar un bookdown sobre plotly.

p1 <- p + facet_grid(cols = vars(Species)) 
ggplotly(p1)
p <- ggplot(mpg, aes(class)) +  geom_bar(fill = "steelblue") + coord_flip()
ggplotly(p)

Aquƭ puedes ver un galerƭa de ejemplos de grƔficos interactivos hechos con R. Aquƭ un post, ya de 2015, para iniciarse un poco en estos temas.

Hay muchos paquetes que permiten hacer grƔficos interactivos en R, por ejemplo leaflet, que permite hacer mapas interactivos muy fƔcilmente.

library(leaflet)
m <- leaflet()
m <- addTiles(m)
m <- addMarkers(m, lng = 174.768, lat =-36.852, popup = "The birthplace of R")
m

Todos estos paquetes9 que permiten hacer grƔficos interactivos a travƩs de htmlwidgets se pueden consultar en: http://gallery.htmlwidgets.org/. Dos paquetes que no estƔn en la gallery: Apexcharts y TSplotly.

En Interactive web-based data visualization with R, plotly, and shiny explican mÔs detalladamente como hacer grÔficos interactivos en R. Tendré que releer su sección Saving and embedding HTML para mostrar en el tutorial alguno de los grÔficos dinÔmicos que he hecho, pero ahora tengo prisa, la próxima clase es pronto y empezamos sí o sí ggplot2.

Hadley tambiƩn estƔ escribiendo un bookdown sobre Shiny: Mastering Shiny. En palabras de Hadley: Shiny is a framework for creating web applications using R code. Para ver que significa esto puedes ver la siguiente galerƭa con ejemplos de aplicaciones shiny.

gganimate

gganimate es un paquete que no hace grÔficos dinÔmicos, pero permite animar grÔficos mediante la creación creación de secuencias de grÔficos. Mejor que explicarlo visita su sección de ejemplos en su wiki: https://github.com/thomasp85/gganimate/wiki. Por ejemplo este ejemplo o este ejemplo de jurgol. En este post explican como usarlo.

Un ejemplo con los datos de gapminder

library(tidyverse)
library(gapminder)
library(gganimate)
gapmider_europe <- gapminder %>% filter(continent == "Europe")
ggplot(gapmider_europe, aes(gdpPercap, lifeExp, size = pop, colour = country)) +
  geom_point(alpha = 0.7, show.legend = FALSE) +
  scale_colour_manual(values = country_colors) +
  scale_size(range = c(2, 12)) +
  scale_x_log10() +
  facet_wrap(~continent) +
  # Here comes the gganimate specific bits
  labs(title = 'Year: {frame_time}', x = 'GDP per capita', y = 'life expectancy') +
  transition_time(year) +
  ease_aes('linear') 

AquĆ­ tienes un ejemplo para hacerlo para los cinco continentes.


Biblio/ejemplos/recursos

Son recursos que he visto o utilizado mientras escribĆ­a esta notas, y no quiero olvidarlos:

Por Ćŗltimo 3 repos de Github asociados al proyecto Tidytuesday. Absolutamente impresionantes!!!

Mas biblio



  1. El repo donde se aloja el código para recrear el libro estĆ” aquĆ­ā†©ļøŽ

  2. Tienes que entender porque son equivalentes. Busca la ayuda de la función y verĆ”s que el primer argumento de la función es data, asĆ­ que si, como ocurre en la segunda expresión, no ponemos el nombre del argumento de la función, R tomarĆ” iris como el valor del primer argumento. Parece un trabalenguas pero es importanteā†©ļøŽ

  3. Si alguno de vosotros me pregunta por la verdad adoptarĆ© semblante hierĆ”tico y sólo dirĆ© lo siguiente: geom_point() is a short-hand for layer(geom = ā€œpointā€, stat = ā€œidentityā€, position = ā€œidentityā€) ó apelarĆ© a la canción de Albert Pla, segĆŗn me de.ā†©ļøŽ

  4. En aes() no sólo puedes poner variables, sino transformaciones de ellas, por ejemplo aes(x = v1 ^ 2, y = v1 / v2). TambiĆ©n se pueden mappear variables con constantes aes(x = 1, colour = ā€œloqueseaā€)ā†©ļøŽ

  5. En este caso sĆ­ quiero que me preguntĆ©is por este detalle. Es algo que ya deberĆ­as saber/intuir pero ā€¦ā†©ļøŽ

  6. En realidad la tercera expresión sĆ­ correrĆ”, pero sólo mostrarĆ” los puntos, no las lineas.ā†©ļøŽ

  7. O marcar/anotar los lirios 2 lirios mĆ”s grandes o el lirio mediano. ĀæLo hacĆ©is?ā†©ļøŽ

  8. Por ejemplo, piensa que lo que se visualiza con geom_smoth() no son los datos originales sino, por ejemplo, la linea de regresión si usamos geom_smoth(method = "lm")ā†©ļøŽ

  9. En el momento de escribir estas notas eran 107 paquetesā†©ļøŽ

LS0tCnRpdGxlOiAiVmlzdWFsaXphY2nDs24gY29uIGdncGxvdDIgKFdJUCkiCmF1dGhvcjogIlBlZHJvIEouIFDDqXJleiIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIgJVknKWAiCiNkYXRlOiAiMjAyMC8wMy8wNSAodXBkYXRlZDogYHIgU3lzLkRhdGUoKWApIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNzczogIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgInN0eWxlc19wanAuY3NzIikgIy1odHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy81NjY4MTg3OS9ob3ctdG8tdXNlLWhlcmUtZm9yLXBhdGhzLXRvLWNzcy1iZWZvcmUtYm9keS1hbmQtYWZ0ZXItYm9kCiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGV4dG1hdGUgCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMyAKICAgIHRvY19mbG9hdDogCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgI2NvZGVfZm9sZGluZzogc2hvdwogICAgZGZfcHJpbnQ6IGthYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQpiaWJsaW9ncmFwaHk6ICJgciBoZXJlOjpoZXJlKCdhc3NldHMnLCAnYmlibGlvLmJpYicpYCIgICMtIGpvb29kZXIuIHNpbmdsZSBxdW90ZXMgaHR0cHM6Ly9jb21tdW5pdHkucnN0dWRpby5jb20vdC91c2UtaGVyZS1oZXJlLWZ1bmN0aW9uLWluLXlhbWwtb3B0aW9uLzE4NjY3LzkKLS0tCgpgYGB7ciBjaHVua19zZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUgPSBGQUxTRSwgY2FjaGUucGF0aCA9ICIvY2FjaGVzLyIsIGNvbW1lbnQgPSAiIz4iLAogICAgICAgICAgICAgICAgICAgICAgI2ZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQ9IDcsICAgCiAgICAgICAgICAgICAgICAgICAgICAjb3V0LndpZHRoID0gNywgb3V0LmhlaWdodCA9IDcsCiAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9IFRSVUUsICBmaWcuc2hvdyA9ICJob2xkIiwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hc3AgPSA3LzksIG91dC53aWR0aCA9ICI2MCUiLCBmaWcuYWxpZ24gPSAiY2VudGVyIikKYGBgCgoKCmBgYHtyIG9wdGlvbnNfc2V0dXAsIGVjaG8gPSBGfQpvcHRpb25zKHNjaXBlbiA9IDk5OSkgIy0gcGFyYSBxdWl0YXIgbGEgbm90YWNpb24gY2llbnRpZmljYQpgYGAKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoaGVyZSkKbGlicmFyeSh0aWR5dmVyc2UpCiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInRob21hc3A4NS9wYXRjaHdvcmsiKQpsaWJyYXJ5KHBhdGNod29yaykKYGBgCgpgYGB7ciBrbGlwcHksIGVjaG8gPSBGQUxTRX0Ka2xpcHB5OjprbGlwcHkocG9zaXRpb24gPSBjKCJ0b3AiLCAicmlnaHQiKSkgIy0gcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInJsZXN1ci9rbGlwcHkiKQpgYGAKCgojIyAxLiBJbnRyb2R1Y2Npw7NuCgpMb3MgZ3LDoWZpY29zIGUgaW5mb2dyYWbDrWFzIHNvbiwgY2FkYSB2ZXogbcOhcywgdW5hIHBhcnRlIGltcG9ydGFudGUgZGUgbGEgbWF5b3LDrWEgZGUgdGV4dG9zIGVzY3JpdG9zLCB5YSBzZWFuIGVzdG9zICB1biBpbmZvcm1lIHTDqWNuaWNvLCB1biBhcnTDrWN1bG8gZGUgcGVyacOzZGljbyBvIHVuIFRGRy4gRW4gZWwgY2FzbyBkZSBsYSBjaWVuY2lhIGRlIGRhdG9zLCBjb21vIHB1ZWRlIHZlcnNlIGFiYWpvIGVuIGxhIGluZm9ncmFmw61hLCBlbCBhbsOhbGlzaXMgeSBsYSBleHBsb3JhY2nDs24gZGUgbG9zIGRhdG9zIGVzIHVuIHByb2Nlc28gaXRlcmF0aXZvIGVuIGVsIHF1ZSBsYSB2aXN1YWxpemFjacOzbiB5IGxhIGdlbmVyYWNpw7NuIGRlIGdyw6FmaWNvcyBvY3VwYSB1biBsdWdhciBkZXN0YWNhZG8uCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmFsaWduID0gImNlbnRlciIsIGZpZy5jYXAgPSAiUiBmb3IgRGF0YSBTY2llbmNlIChodHRwOi8vcjRkcy5oYWQuY28ubnovKSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA2X2ltZ18wMV9leHBsb3JlLXZpc3VhbGljZS5wbmciKSApIApgYGAKCgoKVW5vIGRlIGxvcyBpbnN0cnVtZW50b3MgeSB0YXJlYXMgZnVuZGFtZW50YWxlcyBkZSB1biBjaWVudMOtZmljbyBkZSBkYXRvcyBlcyBsYSBjYXBhY2lkYWQgZGUgcmVhbGl6YXIgdmlzdWFsaXphY2lvbmVzIGRlIGRhdG9zIGFwcm9waWFkYXMgeSBjb252aW5jZW50ZXMuIEVsIGFuw6FsaXNpcyBncsOhZmljbyBubyBzb2xvIGF5dWRhIGVuIGxhIGV4cGxvcmFjacOzbiB5IGNvbXByZW5zacOzbiBkZSBsb3MgZGF0b3MsIHNpbm8gcXVlIGVzIGZ1bmRhbWVudGFsIGEgbGEgaG9yYSBkZSBtb3N0cmFyIGxhcyBwb3NpYmxlcyByZWxhY2lvbmVzIGVudHJlIHZhcmlhYmxlcywgZGVzY3VicmlyIHJlbGFjaW9uZXMgbyBwYXRyb25lcyBvY3VsdG9zLCB5IGRlc2NhcnRhciBvIHN1Z2VyaXIgbnVldmFzIHByZWd1bnRhcyBzb2JyZSBsb3MgZGF0b3MuIAoKCgpFbCBlbnRvcm5vIFIgdGllbmUgZGl2ZXJzb3Mgc2lzdGVtYXMgcGFyYSB2aXN1YWxpemFyIGRhdG9zLCBsb3MgZG9zIG3DoXMgdXRpbGl6YWRvcyBzb24gZWwgc2lzdGVtYSBncsOhZmljbyBkZSBSLWJhc2UgeSBlbCBlY29zaXN0ZW1hIGFzb2NpYWRvIGFsIHBhcXVldGUgYGdncGxvdDJgLiBQb3IgZGl2ZXJzYXMgcmF6b25lcywgZW4gZWwgY3Vyc28gdXNhcmVtb3MgZWwgZW50b3JubyBgZ2dwbG90MmAgcGFyYSBoYWNlciBudWVzdHJvcyBncsOhZmljb3M7IGRlIGhlY2hvLCBlbiBsYSBhY3R1YWxpZGFkIGBnZ3Bsb3QyYCwgZGFkYSBzdSByYXBpZGV6IGVuIGxhIGl0ZXJhY2nDs24gZW50cmUgZ3LDoWZpY29zLCB2ZXJzYXRpbGlkYWQgeSBsYSBjdWlkYWRhIGVzdMOpdGljYSBxdWUgdGllbmVuIHN1cyBncsOhZmljb3MsIHNlIGhhIGNvbnZlcnRpZG8sIGFsIG1lbm9zIHBvciBlbCBtb21lbnRvLCBlbiBlbCBzaXN0ZW1hIGVzdMOhbmRhciBwYXJhIGhhY2VyIGdyw6FmaWNvcyBlbiBSLiBQb3IgZWplbXBsbywgW8K/Y8OzbW8gY3Jlw6lpcyBxdWUgaGFjZSBsYSBCQkMgc3VzIGdyw6FmaWNvcz9dKGh0dHBzOi8vbWVkaXVtLmNvbS9iYmMtdmlzdWFsLWFuZC1kYXRhLWpvdXJuYWxpc20vaG93LXRoZS1iYmMtdmlzdWFsLWFuZC1kYXRhLWpvdXJuYWxpc20tdGVhbS13b3Jrcy13aXRoLWdyYXBoaWNzLWluLXItZWQwYjM1NjkzNTM1KXt0YXJnZXQ9Il9ibGFuayJ9LCBldmlkZW50ZW1lbnRlIGNvbiBSIHkgYGdncGxvdDJgLiBQdWVkZXMgdmVyIHVubyBkZSBzdXMgcmVwb3NpdG9yaW9zIFthcXXDrV0oaHR0cHM6Ly9naXRodWIuY29tL2JiYy9iYnBsb3Qpe3RhcmdldD0iX2JsYW5rIn0geSBzdSBjb29rYm9vayBbYXF1w61dKGh0dHA6Ly9iaXQubHkvYmJjZ2dwbG90Mil7dGFyZ2V0PSJfYmxhbmsifS4gCgoKQ29uIGBnZ3Bsb3QyYCBlcyBzZW5jaWxsbyBoYWNlciBncsOhZmljb3MgY29uIGNhbGlkYWQgcGFyYSBzZXIgcHVibGljYWRvcyBvIG1vc3RyYWRvcywgYWRlbcOhcyBkZSBxdWUsIGRhZGEgc3Ugc2ludGF4aXMgbW9kdWxhciwgaGFjZSBzZW5jaWxsbyBlbCByZXV0aWxpemFyIGxvcyBncsOhZmljb3MgZHVyYW50ZSBlbCBwcm9jZXNvIGRlIGFuw6FsaXNpcy4gRWwgcGFxdWV0ZSBgZ2dwbG90MmAgZnVlIGluaWNpYWxtZW50ZSBkZXNhcnJvbGxhZG8gcG9yIFtIYWRsZXkgV2lja2hhbV0oaHR0cDovL2hhZGxleS5uei8pe3RhcmdldD0iX2JsYW5rIn0sIHBlcm8gYWN0dWFsbWVudGUgZWwgZWNvc2lzdGVtYSBnZ3Bsb3QgZXMgZWwgcmVzdWx0YWRvIGRlIHRvZGEgdW5hIGNvbXVuaWRhZCBkZSB1c3VhcmlvcyBxdWUgY29udHJpYnV5ZSBhIGVucmlxdWVjZXIgZWwgc2lzdGVtYSBncsOhZmljbyBjb24gc3VzIGV4dGVuc2lvbmVzIHkgcGFxdWV0ZXMgYXV4aWxpYXJlcy4KCgpFbiBwYWxhYnJhcyBkZSBIYWRsZXkgZW4gc3UgW2xpYnJvIHNvYnJlIGdncGxvdDJdKGh0dHBzOi8vZ2dwbG90Mi1ib29rLm9yZy8pe3RhcmdldD0iX2JsYW5rIn06Cgo+IGdncGxvdDIgaXMgYW4gUiBwYWNrYWdlIGZvciBwcm9kdWNpbmcgc3RhdGlzdGljYWwsIG9yIGRhdGEsIGdyYXBoaWNzLCBidXQgaXQgaXMgdW5saWtlIG1vc3Qgb3RoZXIgZ3JhcGhpY3MgcGFja2FnZXMgYmVjYXVzZSBpdCBoYXMgYSBkZWVwIHVuZGVybHlpbmcgZ3JhbW1hci4gVGhpcyBncmFtbWFyLCBiYXNlZCBvbiB0aGUgR3JhbW1hciBvZiBHcmFwaGljcyAoV2lsa2luc29uIDIwMDUpLCBpcyBtYWRlIHVwIG9mIGEgc2V0IG9mIGluZGVwZW5kZW50IGNvbXBvbmVudHMgdGhhdCBjYW4gYmUgY29tcG9zZWQgaW4gbWFueSBkaWZmZXJlbnQgd2F5cy4gVGhpcyBtYWtlcyBnZ3Bsb3QyIHZlcnkgcG93ZXJmdWwgYmVjYXVzZSB5b3UgYXJlIG5vdCBsaW1pdGVkIHRvIGEgc2V0IG9mIHByZS1zcGVjaWZpZWQgZ3JhcGhpY3MsIGJ1dCB5b3UgY2FuIGNyZWF0ZSBuZXcgZ3JhcGhpY3MgdGhhdCBhcmUgcHJlY2lzZWx5IHRhaWxvcmVkIGZvciB5b3VyIHByb2JsZW0uIFRoaXMgbWF5IHNvdW5kIG92ZXJ3aGVsbWluZywgYnV0ICoqYmVjYXVzZSB0aGVyZSBpcyBhIHNpbXBsZSBzZXQgb2YgY29yZSBwcmluY2lwbGVzIGFuZCB2ZXJ5IGZldyBzcGVjaWFsIGNhc2VzLCBnZ3Bsb3QyIGlzIGFsc28gZWFzeSB0byBsZWFybioqIChhbHRob3VnaCBpdCBtYXkgdGFrZSBhIGxpdHRsZSB0aW1lIHRvIGZvcmdldCB5b3VyIHByZWNvbmNlcHRpb25zIGZyb20gb3RoZXIgZ3JhcGhpY3MgdG9vbHMpLgoKClPDrSwgY29uIGBnZ3Bsb3QyYCBlcyAiZsOhY2lsIiBoYWNlciByw6FwaWRhbWVudGUgZ3LDoWZpY29zIGRlIGNhbGlkYWQsIFBFUk8gZG9taW5hciB0b2RvcyBsb3MgZGV0YWxsZXMgZGVsIHBhcXVldGUgc8OtIHF1ZSBlcyBjb21wbGljYWRvLCBwZXJvIG5vIG5vcyBoYWNlIGZhbHRhIGNvbm9jZXJsbyB0b2RvLiBBZGVtw6FzLCBoYXkgcXVlIHRlbmVyIGVuIGN1ZW50YSBxdWUgYGdncGxvdDJgIGVzIHVuIHBhcXVldGUvZW50b3JubyBlbiBjb25zdGFudGUgZXZvbHVjacOzbi4gQWN0dWFsbWVudGUgZXN0w6EgZW4gbGEgdmVyc2nDs24gMy4yLjEuIEJ1ZW5vLCBlbiBtYXJ6byBkZSAyMDIwIGFwYXJlY2nDsyBsYSB2ZXJzacOzbiAzLjMuMC4gCgoKUGFyYSBlbnRlbmRlciBlc3RhIGlkZWEgZGUgbGEgY29uc3RhbnRlIGV2b2x1Y2nDs24geSBlbCBwYXBlbCBxdWUgdGllbmUgbGEgY29tdW5pZGFkIGRlIHVzdWFyaW9zIGVuIGVsIGRlc2Fycm9sbG8gZGUgUiB5IHN1cyBwYXF1ZXRlcyBwdWVkZXMgbGVlciBbZXN0ZSB0d2VldF0oaHR0cHM6Ly90d2l0dGVyLmNvbS9DbGF1c1dpbGtlL3N0YXR1cy8xMTY2MzU2MjEwNzgzODcwOTc2KXt0YXJnZXQ9Il9ibGFuayJ9IHkgbGFzIHJlc3B1ZXN0YXMgYSDDqWwuIEVuIGVsIHR3ZWV0IHPDs2xvIHNlIGFudW5jaWEgdW5hIHBlcXVlw7FhIG1lam9yYSBlbiBjb21vIGBnZ3Bsb3QyYCBnZXN0aW9uYSBsb3MgdMOtdHVsb3MgZGUgbG9zIGdyw6FmaWNvcyBwZXJvIGdlbmVyYSByZWFjY2lvbmVzIGVuIGxhIGNvbXVuaWRhZCBkZSB1c3Vhcmlvcy4gCgpMYSByZWRlcyBzb2NpYWxlcyBwdWVkZW4gaGFjZXJzZSBlY28gZGUgbGEgZXZvbHVjacOzbiBkZSB1biBwYXF1ZXRlLCBwZXJvIGRvbmRlIGhhYml0dWFsbWVudGUgc2UgcHJvZHVjZSBsYSBkaXNjdXNpw7NuL2NvbGFib3JhY2nDs24gZW50cmUgdXN1YXJpb3MgZXMgZW4gcGxhdGFmb3JtYXMgY29tbyBbR2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vKXt0YXJnZXQ9Il9ibGFuayJ9LiBQb3IgZWplbXBsbywgcHVlZGVzIHZlciBjb21vIHNlIGdlc3TDsyBlc3TDoSBwZXF1ZcOxYSAgbWVqb3JhIFthcXXDrV0oaHR0cHM6Ly9naXRodWIuY29tL3RpZHl2ZXJzZS9nZ3Bsb3QyL2lzc3Vlcy8zMjUyKXt0YXJnZXQ9Il9ibGFuayJ9LiBGdWUgbGEgaXNzdWUgMzI1MiBkZSBgZ2dwbG90MmAuIE90cm8gZWplbXBsbywganVzdG8gY3VhbmRvIGVzdGFiYSBlc2NyaWJpZW5kbyBlc3RlIHDDoXJyYWZvLCBsZcOtIFtlc3RlIG90cm8gdHdlZXRdKGh0dHBzOi8vdHdpdHRlci5jb20vdGhvbWFzcDg1L3N0YXR1cy8xMTY2NjA5NTI5NzcxMTAyMjA4KXt0YXJnZXQ9Il9ibGFuayJ9IGFudW5jaWFuZG8gb3RyYSBtZWpvcmEgZW4gYGdncGxvdDJgLgoKCkxhIHDDoWdpbmEgd2ViIGRlIGBnZ3Bsb3QyYCBwdWVkZXMgZW5jb250cmFybGEgW2FxdcOtXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0uIEVuIGVsbGEgcHVlZGVzIGVuY29udHJhciBkb2N1bWVudG9zIGRlIGF5dWRhIHkgbGEgW3JlZmVyZW5jaWEgb2ZpY2lhbF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0uIFBhcmEgZGFydGUgY3VlbnRhIGRlIHRvZG8gbG8gcXVlIHNlIHB1ZWRlIGhhY2VyIGNvbiBlbCBlY29zaXN0ZW1hIGdncGxvdCAgdmlzaXRhIFtlc3RhIHDDoWdpbmFdKGh0dHBzOi8vZXh0cy5nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvZ2FsbGVyeS8pe3RhcmdldD0iX2JsYW5rIn0gZG9uZGUgcG9kcsOhcyB2ZXIgbG9zIDc5ICJwYXF1ZXRlcyBhdXhpbGlhcmVzIiBvIGV4dGVuc2lvbmVzIGEgYGdncGxvdDJgLgoKClBhcmEgaGFjZXIgImJ1ZW5vcyIgZ3LDoWZpY29zIGNvbiBgZ2dwbG90MiBgbm8gc8OzbG8gZXMgbmVjZXNhcmlvIGVudGVuZGVyIGxhIHNpbnRheGlzIHkgbG9zIHBvcm1lbm9yZXMgZGVsIHBhcXVldGUsIHNpbm8gcXVlIHF1aXrDoXMgc2UgbmVjZXNpdGUgYWxnbyBtw6FzLiBQb3IgZWplbXBsbywgYWxnbyBkZSBleHBlcmllbmNpYSB5IGNpZXJ0YSBjYXBhY2lkYWQgdmlzdWFsIHkgZXN0w6l0aWNhOyBpbmNsdXNvIGhheSBxdWllbiBkaWNlIHF1ZSBoYWNlciBidWVub3MgZ3LDoWZpY29zIGVzIHVuIGFydGUuIFBhcmEgaW50ZW50YXIgbWVqb3JhciB2dWVzdHJvcyBncsOhZmljb3MgbyBldml0YXIgY2llcnRvcyBlcnJvcmVzLCBbYXF1w61dKGh0dHBzOi8vcm9iamh5bmRtYW4uY29tL2h5bmRzaWdodC9ncmFwaGljcy8pe3RhcmdldD0iX2JsYW5rIn0gdGVuw6lpcyBhbGd1bmFzIHJlZ2xhcy9jb25zZWpvcywgeSBbYXF1w61dKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL3RlYWNoaW5nLWFuZC1sZWFybmluZy1tYXRlcmlhbHMtZm9yLWRhdGEtdmlzdWFsaXphdGlvbi8pe3RhcmdldD0iX2JsYW5rIn0gdW4gY3Vyc28gY29tcGxldG8gc29icmUgdmlzdWFsaXphY2nDs25eW0VsIHJlcG8gZG9uZGUgc2UgYWxvamEgZWwgY8OzZGlnbyBwYXJhIHJlY3JlYXIgZWwgbGlicm8gZXN0w6EgW2FxdcOtXShodHRwczovL2dpdGh1Yi5jb20vY2xhdXN3aWxrZS9kYXRhdml6KV0gY29uIFtib29rZG93biBpbmNsdWlkb10oaHR0cHM6Ly9zZXJpYWxtZW50b3IuY29tL2RhdGF2aXovKXt0YXJnZXQ9Il9ibGFuayJ9LgoKRW50ZW5kZXLDoXMgbXV5IGJpZW4gcXVlIGhhY2VyIGJ1ZW5vcyBncsOhZmljb3MgZXhpZ2UgY29ub2NpbWllbnRvIHkgbXVjaG8gdHJhYmFqbyBhbCB2ZXIgZWwgdmlkZW8gZGUgW2VzdGUgcG9zdF0oaHR0cHM6Ly9rYXJhbWFuLmlzL2Jsb2cvMjAyMC8wNy9tYWtpbmctb2YvKSwgZG9uZGUgc2UgdmVuIGxhcyB2ZXJzaW9uZXMgcHJldmlhcyBwYXJhIHF1ZSBhbCBmaW5hbCBzYWxpZXNlIGVzdGEgcHJlY2lvc3VyYSBkZSBnxZVhZmljbzoKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBmaWcuYXNwID0gNy85fQp0d2VldHJtZDo6dHdlZXRfZW1iZWQoImh0dHBzOi8vdHdpdHRlci5jb20vZ2Vva2FyYW1hbmlzL3N0YXR1cy8xMjQ3NTg2Mzk1ODc2NzQxMTIwIiwgdGhlbWUgPSAibGlnaHQiLCBhbGlnbiA9ICJjZW50ZXIiLCBtYXh3aWR0aCA9IDQwMCkKYGBgCgpPdHJvIGVqZW1wbG8gZGVsIG1ha2luZy1vZiBkZSB1biBncsOhZmljbyBwdWVkZXMgdmVybG8gW2FxdcOtXShodHRwczovL3R3aXR0ZXIuY29tL01haWFQZWxsZXRpZXIvc3RhdHVzLzEyODc5MjI4NTU5MjYyMTQ2NTcpLgoKCk90cm9zIGRvcyBsaWJyb3Mgc29icmUgdmlzdWFsaXphY2nDs24gY29uIGVsIGPDs2RpZ28gZGUgbG9zIGVqZW1wbG9zIGhlY2hvcyBjb24gYGdncGxvdDJgLCBbYXF1w61dKGh0dHBzOi8vc29jdml6LmNvL2luZGV4Lmh0bWwjcHJlZmFjZSkgeSBbYXF1w61dKGh0dHBzOi8vc29jdml6LmNvL2xvb2thdGRhdGEuaHRtbCNsb29rYXRkYXRhKXt0YXJnZXQ9Il9ibGFuayJ9LiBGaW5hbG1lbnRlLCBhbGd1bm9zIGNvbnNlam9zIGRlIGxhIFtCQkNdKGh0dHBzOi8vYmJjLmdpdGh1Yi5pby9yY29va2Jvb2svKXt0YXJnZXQ9Il9ibGFuayJ9IHNvYnJlIHZpc3VhbGl6YWNpw7NuLgoKQ29tbyB0YW1iacOpbiBzZSBhcHJlbmRlIGxvcyBlcnJvcmVzLCBbYXF1w61dKGh0dHBzOi8vbWVkaXVtLmVjb25vbWlzdC5jb20vbWlzdGFrZXMtd2V2ZS1kcmF3bi1hLWZldy04Y2RkOGE0MmQzNjgpe3RhcmdldD0iX2JsYW5rIn0gdGllbmVzIHVuIGFydGljdWxvIGRlIFRoZSBFY29ub21pc3QgZG9uZGUgbXVlc3RyYW4gZXJyb3JlcyBxdWUgZWxsb3MgbWlzbW9zIGhhbiBjb21ldGlkbyBoYWNpZW5kbyBncsOhZmljb3MuIEFkZW3DoXMgcHVlZGVuIGRlc2NhcmdhcnNlIGxvcyBkYXRvcy5PdHJvIGVqZW1wbG8gZGUgZ3LDoWZpY28gdW4gcG9xdWl0byB0ZW5kZW5jaW9zbywgZXN0YSB2ZXogZGVsIFdhc2hpbmd0b24gUG9zdDoKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBmaWcuYXNwID0gNy85fQp0d2VldHJtZDo6dHdlZXRfZW1iZWQoImh0dHBzOi8vdHdpdHRlci5jb20vYW5keXByZXNzbWFuL3N0YXR1cy8xMTIyNTEyNjY0ODEzNjA0ODY1IiwgdGhlbWUgPSAibGlnaHQiLCBhbGlnbiA9ICJjZW50ZXIiLCBtYXh3aWR0aCA9IDQwMCkKYGBgCgo8YnI+CgojIyAyLiBJZGVhcyBiw6FzaWNhcyBzb2JyZSBnZ3Bsb3QyCgpZYSBzZSBkaWpvIHF1ZSBgZ2dwbG90MmAgZXMgdW4gcGFxdWV0ZSBSIGRlc2Fycm9sbGFkbyBwb3IgSGFkbGV5IFdpY2toYW0sIGF1bnF1ZSBhY3R1YWxtZW50ZSBlcyBlbCByZXN1bHRhZG8gZGUgbGEgY29sYWJvcmFjacOzbiBkZSBtw7psdGlwbGVzIGRlc2Fycm9sbGFkb3Jlcy4gYGdncGxvdDJgIGltcGxlbWVudGEgZW4gUiBbVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3NdKGh0dHBzOi8vd3d3LnNwcmluZ2VyLmNvbS9ncC9ib29rLzk3ODAzODcyNDU0NDcpIGRlIEwuIFdpbGtpbnNvbiwgdW4gc2lzdGVtYSBjb2hlcmVudGUgcGFyYSBkZXNjcmliaXIgeSBjb25zdHJ1aXIgZ3LDoWZpY29zLiBFbCDDqW5mYXNpcyBkZSBnZ3Bsb3QyIGVzdMOhIGVuIGxhIGV4cGxvcmFjacOzbiByw6FwaWRhIGRlIGRhdG9zLCBlc3BlY2lhbG1lbnRlIGRlIGRhdG9zIGRlIGFsdGEgZGltZW5zaW9uYWxpZGFkLiBDb24gYGdncGxvdDJgIGVzIHNlbmNpbGxvIGlyIHRyYW5zZm9ybWFuZG8gZWwgZ3LDoWZpY28gbWllbnRyYXMgc2UgdmFuIGFuYWxpemFuZG8gbG9zIGRhdG9zLiAKCgoKUGFyYSBlbXBlemFyIGEgZW50ZW5kZXIgbGEgImZpbG9zb2bDrWEiIGRlIGBnZ3Bsb3QyYCwgb3MgcGxhbnRlbyB1bmEgcHJlZ3VudGEgbWVkaW8gcmV0w7NyaWNhOiDCv3F1w6kgdmVtb3MgZW4gZWwgZ3LDoWZpY28gZGUgYWJham8/CgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjYwJSJ9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llcykpICsgZ2VvbV9wb2ludCgpCmBgYAoKClB1ZXMgc8OtLCBlcyB1biBncsOhZmljbyBkZSBwdW50b3MgeSBub3MgYXl1ZGEgYSB2ZXIgbGFzIHJlbGFjaW9uZXMgcXVlIGV4aXN0ZW4gZW50cmUgMyB2YXJpYWJsZXMuIEVzdGFtb3MgaGFiaXR1YWRvcyBhIGVsbG8sIHBlcm8gdmFtb3MgYSBwZW5zYXIgZW4gZWwgZ3LDoWZpY28gZGVzZGUgbGEgw7NwdGljYSBkZSAiVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MiIGltcGxlbWVudGFkYSBlbiBlbCBwYXF1ZXRlIGBnZ3Bsb3QyYC4gCgpFbiBudWVzdHJvIGdyw6FmaWNvIHNlIHJlcHJlc2VudGFuIHBvciBtZWRpbyBkZSBwdW50b3MsIGVuIGVsIGVzcGFjaW8gWC1ZLCB5IG1lZGlhbnRlIGxvcyBkaXN0aW50b3MgY29sb3JlcyBkZSBsb3MgcHVudG9zLCBsYXMgb2JzZXJ2YWNpb25lcyBkZSAzIHZhcmlhYmxlcy4gQmllbiwgbmFkYSBtdXkgbm92ZWRvc28sIHRvZG9zIGxvcyBzaXN0ZW1hcyBncsOhZmljb3MgaGFjZW4gZXN0ZSB0aXBvIGRlIGdyw6FmaWNvcy4gTG9zIGdyw6FmaWNvcyBkZSBgZ2dwbG90MmAgc2UgcmVhbGl6YW4gbWVkaWFudGUgbGEgc3VwZXJwb3NpY2nDs24gZGUgZWxlbWVudG9zL2NhcGFzLiBQb2RlbW9zIHBlbnNhciBxdWUgZWwgZ3LDoWZpY28gcXVlIGhlbW9zIHZpc3RvIGVzIHVuYSBjYXBhLiDCv0PDs21vIGNyZWFtb3MgZXN0ZSBncsOhZmljbyBvIGNhcGEgZW4gYGdncGxvdDJgPwoKUHVlcywgdW5hIGRlIGxhcyBwcmluY2lwYWxlcyBpZGVhcyBwYXJhIGVudGVuZGVyIGBnZ3Bsb3QyYCBlcyBxdWUgY2FkYSBjYXBhIGRlIHVuIGdyw6FmaWNvIHRpZW5lIDMgY29tcG9uZW50ZXMgbyBlbGVtZW50b3MgcHJpbmNpcGFsZXM6CgoKICAtICoqbG9zIGRhdG9zKiogcXVlIHNlIHZhbiBhIHJlcHJlc2VudGFyICAoc2VuY2lsbG8sIHBhcmEgaGFjZXIgdW4gZ3LDoWZpY28gaGFjZW4gZmFsdGEgZGF0b3MpLiBQYXJhIGVsbG8gdXRpbGl6YXJlbW9zIGdlbmVyYWxtZW50ZSBsYSBmdW5jacOzbiBgZ2dwbG90KClgCiAgCiAgLSAqKnVuIGNvbmp1bnRvIGRlIHByb3BpZWRhZGVzIGVzdMOpdGljYXMgYXNvY2lhZGFzIGEgYWxndW5hIHZhcmlhYmxlIGRlbCBjb25qdW50byBkZSBkYXRvcyoqLiBQb3IgZWplbXBsbywgbGEgdmFyaWFibGUgU2VwYWwuTGVuZ2h0IGVzdMOhIGFzb2NpYWRhIGFsIGVqZSBYLCBhbGEgcG9zaWNpw7NuIGVuIGVsIGVqZSBYLiBQb3Igc3UgcGFydGUsIGVsIGNvbG9yIGRlIGxvcyBwdW50b3MgKG90cmEgY2FyYWN0ZXLDrXN0aWNhIHZpc3VhbCBvIGVzdMOpdGljYSkgZXN0w6EgYXNvY2lhZG8gYSBsb3MgdmFsb3JlcyBkZSBsYSB2YXJpYWJsZSBTcGVjaWVzLiBFcyBkZWNpciwgdXNhbmRvIGxhIHRlcm1pbm9sb2fDrWEgZGUgYGdncGxvdDJgLCBsYXMgZGlzdGludGFzIHZhcmlhYmxlcyBlc3TDoW4gYXNvY2lhZGFzIG8gbWFwZWFkYXMgYSBkZXRlcm1pbmFkYXMgY2FyYWN0ZXLDrXN0aWNhcyBlc3TDqXRpY2FzLiBFbCBtYXBlbyBkZSB2YXJpYWJsZXMgY29uIGVzdMOpdGljYXMgc2UgaGFyw6EgY29uIGxhIGZ1bmNpw7NuIGBhZXMoKWAgIGRlICoqYWVzKip0aGV0aWNzLiAoZXN0byB5YSBubyBlcyB0YW4gZXN0w6FuZGFyLCBzZSBleHBsaWNhIGVuIGJyZXZlKQogIAogIC0gKiplbCBlbGVtZW50byBnZW9tw6l0cmljbyBxdWUgc2UgdmEgYSByZXByZXNlbnRhcioqLiBFbiBudWVzdHJvIGNhc28gZWwgZWxlbWVudG8gZ2VvbcOpdHJpY28gcXVlIHNlIHV0aWxpemEgcGFyYSByZXByZXNlbnRhciBsb3MgdmFsb3JlcyBkZSBsYXMgdmFyaWFibGVzIHNvbiBsb3MgcHVudG9zLCBwZXJvIHBvZHLDrWFuIGhhYmVyIHNpZG8gbGFzIGxpbmVhcyBvIGxhcyBiYXJyYXMgLi4uIFBhcmEgZXNwZWNpZmljYXIgZWwgZWxlbWVudG8gZ2VvbcOpdHJpY28gcXVlIHZhbW9zIGEgdXNhciBlbiBudWVzdHJvIGdyw6FmaWNvIHNlIHV0aWxpemEgbGEgZmFtaWxpYSBkZSBmdW5jaW9uZXMgYGdlb21feHgoKWA7IHBvciBlamVtcGxvIGBnZW9tX3BvaW50KClgIHNpIHF1ZXJlbW9zIHB1bnRvcywgYGdlb21fbGluZSgpYCBzaSBxdWVyZW1vcyBxdWUgbGFzIHJlbGFjaW9uZXMgZW50cmUgbGFzIHZhcmlhYmxlcyBzZSByZXByZXNlbnRlbi92aXN1YWxpY2VuIGNvbW8gbGluZWFzLgogIApBc8OtIGVuIGFic3RyYWN0byBwdWVkZSBzZXIgY29tcGxpY2FkbyBlbnRlbmRlciBkZWwgdG9kbyBxdWUgcXVpZXJlIGRlY2lyIHRvZG8gZXN0by4gVmFtb3MgYSB2ZXJsbyBjb24gZWplbXBsb3MgY29uY3JldG9zLiBEZSBtb21lbnRvLCBwYXJhIGV4cGxpY2FyIGxhcyBwcmluY2lwYWxlcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGBnZ3Bsb3QyYCB1dGlsaXphcsOpIHVuIGNvbmp1bnRvIGRlIGRhdG9zIGZhbW9zbywgcGVybyAgb2RpYWRvIHBvciBhbGd1bm9zLCBwb3IgaGFiZXIgc2lkbyB1dGlsaXphZG8gZW4gbnVtZXJvc29zIGVqZW1wbG9zIHkgY3Vyc29zOiBlbCBbaXJpcyBkYXRhc2V0XShodHRwczovL2VzLndpa2lwZWRpYS5vcmcvd2lraS9Db25qdW50b19kZV9kYXRvc19mbG9yX2lyaXMpLgoKRWwgY29uanVudG8gZGUgZGF0b3MgYGlyaXNgIGNvbnRpZW5lIGRhdG9zIHNvYnJlIDE1MCBmbG9yZXMsIGVuIGNvbmNyZXRvIHNvYnJlIDE1MCBsaXJpb3MuIGBpcmlzYCB0aWVuZSA1IHZhcmlhYmxlcywgNCBkZSBlbGxhcyBtaWRlbiBsYSBsb25naXR1ZCB5IGVsIGFuY2hvIGRlbCBww6l0YWxvIHkgc8OpcGFsbyBkZSBsb3MgMTUwIGxpcmlvcy4gRXN0YXMgNCBwcmltZXJhcyB2YXJpYWJsZXMgc29uIGN1YW50aXRhdGl2YXMgeSBjb250aW51YXM7IG1pZW50cmFzIHF1ZSBsYSBxdWludGEgdmFyaWFibGUgZXMgY2F0ZWfDs3JpY2EsIGluZGljYW5kbyBsYSBjbGFzZSBvIHZhcmllZGFkIGRlIGxvcyBsaXJpb3MsIHlhIHF1ZSBlbiBsb3MgZGF0b3MgaGF5IDMgZXNwZWNpZXMgZGlzdGludGFzIGRlIGxpcmlvcyAoc2V0b3NhLCB2ZXJzaWNvbG9yIHkgdmlyZ2luaWNhKS4gQ29uIGVzdG9zIGRhdG9zLCBjb24gMyBkZSBzdXMgdmFyaWFibGVzLCBzZSBoYSBjcmVhZG8gZWwgZ3LDoWZpY28gcXVlIHZlcyBtw6FzIGFycmliYS4KCjxicj4KCioqUFJJTUVSIEdSw4FGSUNPKio6IFBhcmEgY29tZW56YXIgbnVlc3RyYXMgYW5kYW56YXMgY29uIGBnZ3Bsb3QyYCBpbnRlbnRhcmVtb3MgcmVwbGljYXIgY29uIGPDs2RpZ28gUiBlbCBncsOhZmljbyBkZSBhcnJpYmE7IGF1bnF1ZSBhbCBwcmluY2lwaW8gc8OzbG8gdXRpbGl6YXJlbW9zIDIgdmFyaWFibGVzOiBoYXJlbW9zIHVuIGdyw6FmaWNvIGRlIHB1bnRvcyBkZSBsYSBsb25naXR1ZCBkZWwgc8OpcGFsbyBmcmVudGUgYSBsYSBsb25naXR1ZCBkZWwgcMOpdGFsby4KCkhhY2VyIHVuIGdyw6FmaWNvIGNvbiBgZ2dwbG90MmAgcmVxdWllcmUgZGUgdmFyaWFzIGV0YXBhcywgCgogIC0gbGEgcHJpbWVyYSBkZSBlbGxhcyBjb25zaXN0ZSBlbiB1c2FyIGxhIGZ1bmNpw7NuIGBnZ3Bsb3QoKWAgcGFyYSBpbmljaWFsaXphciBlbCBncsOhZmljby4gCiAgCiAgLSBlbiBzZWd1bmRvIGx1Z2FyLCB0ZW5kcmVtb3MgcXVlIGVzcGVjaWZpY2FyIHF1ZSBjb25qdW50byBkZSBkYXRvcyB1c2FyZW1vcyBlbiBlbCBncsOhZmljby4gCiAgCiAgLSBlbiB0ZXJjZXIgbHVnYXIgdGVuZHJlbW9zIHF1ZSBlc3BlY2lmaWNhciBxdWUgdmFyaWFibGVzIGlyw6FuIGFzb2NpYWRhcyBhIGRldGVybWluYWRvcyBlbGVtZW50b3MgdmlzdWFsZXMgbyBlc3TDqXRpY29zIGRlbCBncsOhZmljbyAKICAKICAtIHBvciDDumx0aW1vLCBlbiBjdWFydG8gbHVnYXIsIHRlbmRyZW1vcyBxdWUgZXNwZWNpZmljYXIgcXVlIHRpcG8gZGUgZ3LDoWZpY28gbyBnZW9tZXRyw61hIHVzYXJlbW9zIHBhcmEgdmlzdWFsaXphciBsYXMgb2JzZXJ2YWNpb25lcy4KICAKICAKVmXDoW1vc2xvIG3DoXMgZGV0ZW5pZGFtZW50ZS4KCkVuIGBnZ3Bsb3QyYCwgcGFyYSBoYWNlciB1biBncsOhZmljbyAqKnNlIGVtcGllemEgU0lFTVBSRSBsbGFtYW5kbyBhIGxhIGZ1bmNpw7NuIGBnZ3Bsb3QoKWAqKi4gU2kgdGVjbGVhbW9zIGBnZ3Bsb3QoKWAgZW4gbGEgY29uc29sYSBvIGVuIHVuIHNjcmlwdCwgcGFyZWNlIHF1ZSBubyBvY3VycmUgbmFkYSwgcGVybyB0cmFzIGxhIGxsYW1hZGEgYSBsYSBmdW5jacOzbiBgZ2dwbG90KClgLCBSIGhhIGNyZWFkbyB1biBvYmpldG8sIHVuIGNvbnRlbmVkb3IgcGFyYSBudWVzdHJvIGZ1dHVybyBncsOhZmljby4gQcO6biBubyB2ZW1vcyBlbCBncsOhZmljbywgZmFsdGFuIGNvc2FzLCBwZXJvIHlhIGxvIGhlbW9zIGluaWNpYWxpemFkby4gU2kgcXVpZXJlcyB2ZXIgZWwgb2JqZXRvL2NvbnRlbmVkb3IgcXVlIGhlbW9zIGNyZWFkbyBjb24gbGEgbGxhbWFkYSBhIGBnZ3Bsb3QoKWAgdGllbmVzIHF1ZSBhc2lnbmFybGUgdW4gbm9tYnJlLCBhc8OtIHBvZHLDoXMgdmVybG8gZW4gbGEgcGVzdGHDsWEgIkVudmlyb25tZW50IiBkZSBSU3R1ZGlvLiAKCgpQYXJhIHZlcmxvIHRpZW5lcyBxdWUgaGFjZXI6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpteV9ncmFmaWNvIDwtIGdncGxvdCgpCmBgYAoKYG15X2dyYWZpY29gLCBlcyB1biBvYmpldG8gUiwgY29uY3JldGFtZW50ZSB1bmEgbGlzdGEgY29uIDkgZWxlbWVudG9zLCBxdWUgdGVuZHJlbW9zIHF1ZSBpciAibGxlbmFuZG8iIHBhcmEgaGFjZXIgbnVlc3RybyBncsOhZmljby4gCgoKR2VuZXJhbG1lbnRlLCBkZW50cm8gZGUgbGEgZnVuY2nDs24gYGdncGxvdCgpYCBzZSBzdWVsZSBlc3BlY2lmaWNhciBlbCBjb25qdW50byBkZSBkYXRvcyBxdWUgdmFzIGEgdXRpbGl6YXIgcGFyYSBoYWNlciBlbCBncsOhZmljby4gQSBkaWZlcmVuY2lhIGRlIGxvcyBncsOhZmljb3MgZGUgUi1iYXNlLCBgZ2dwbG90MmAgbm8gcGVybWl0ZSBncmFmaWNhciB2ZWN0b3JlczogKipsb3MgZGF0b3MgcXVlIHNlIHN1bWluaXN0cmFuIGhhbiBkZSBzZXIgU0lFTVBSRSBkYXRhLmZyYW1lcyoqIG8gc2ltaWxhcmVzLgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChkYXRhID0gaXJpcykKZ2dwbG90KGlyaXMpCmBgYAoKCgpZYSBlc3TDoSwgY29uIGN1YWxxdWllcmEgZGUgbGFzIDIgaW5zdHJ1Y2Npb25lcyBkZSBhcnJpYmEsIHNvbiBlcXVpdmFsZW50ZXNeW1RpZW5lcyBxdWUgZW50ZW5kZXIgcG9ycXVlIHNvbiBlcXVpdmFsZW50ZXMuIEJ1c2NhIGxhIGF5dWRhIGRlIGxhIGZ1bmNpw7NuIHkgdmVyw6FzIHF1ZSBlbCBwcmltZXIgYXJndW1lbnRvIGRlIGxhIGZ1bmNpw7NuIGVzIGRhdGEsIGFzw60gcXVlIHNpLCBjb21vIG9jdXJyZSBlbiBsYSBzZWd1bmRhIGV4cHJlc2nDs24sIG5vIHBvbmVtb3MgZWwgbm9tYnJlIGRlbCBhcmd1bWVudG8gZGUgbGEgZnVuY2nDs24sIFIgdG9tYXLDoSBpcmlzIGNvbW8gZWwgdmFsb3IgZGVsIHByaW1lciBhcmd1bWVudG8uIFBhcmVjZSB1biB0cmFiYWxlbmd1YXMgcGVybyBlcyBpbXBvcnRhbnRlXSwgeWEgaGVtb3MgaW5pY2lhbGl6YWRvIGVsIGdyw6FmaWNvIHkgbGUgaGVtb3MgZGljaG8gcXVlIGRhdG9zIHZhbW9zIGEgdXNhci4gCgpEaWppbW9zIHF1ZSBwYXJhIGNvbWVuemFyIGhhcsOtYW1vcyB1biBncsOhZmljbyBkZSBwdW50b3MgZGUgbGEgdmFyaWFibGUgYFNlcGFsLkxlbmd0aGAgZnJlbnRlIGEgYFBldGFsLkxlbmd0aGA7IGFzw60gcXVlICoqdGVuZW1vcyBxdWUgZGVjaXJsZSBhIGBnZ3Bsb3QyYCBxdWUgdmFyaWFibGVzIGRlIGBpcmlzYCBxdWVyZW1vcyB2aXN1YWxpemFyIHkgY29uIHF1ZSBwcm9waWVkYWRlcyBlc3TDqXRpY2FzIHF1ZXJlbW9zIGFzb2NpYXIgY2FkYSB2YXJpYWJsZSoqLiBQYXJhIGVsbG8gdXRpbGl6YXJlbW9zIGxhIGZ1bmNpw7NuIGBhZXMoKWAgZGVudHJvIGRlIGBnZ3Bsb3QoKWAuIExvIGhhY2Vtb3MgY29uIGxhIHNpZ3VpZW50ZSBleHByZXNpw7NuOgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChpcmlzLCBhZXMoeCA9IFNlcGFsLkxlbmd0aCwgIHkgPSBQZXRhbC5MZW5ndGgpKQpgYGAKCkHDum4gbm8gdmVtb3MgZWwgZ3LDoWZpY28sIHBlcm8gY29uIGBhZXMoKWAgbGUgaGVtb3MgZGljaG8gYSBSL2dncGxvdDIgcXVlIHF1ZXJlbW9zIGFzb2NpYXIvbWFwZWFyIGxhIHZhcmlhYmxlIFNlcGFsLkxlbmd0aCBhbCBlamUgeCwgeSBsYSB2YXJpYWJsZSBQZXRhbC5MZW5ndGggYWwgZWplIHkuIEbDrWphdGUgcXVlLCBlbiBsYSBwZXN0YcOxYSBkZSBncsOhZmljb3MgZGUgUlN0dWRpbywgeWEgdmVtb3MgbG9zIGVqZXMgZGVsIGdyw6FmaWNvOyBhZGVtw6FzLCBmw61qYXRlIHF1ZSBgZ2dwbG90MmAgeWEgaGEgY2FsY3VsYWRvIHBhcmEgbm9zb3Ryb3MgZWwgcmFuZ28gZGUgbGFzIHZhcmlhYmxlcyBwYXJhIGFqdXN0YXIgbG9zIGVqZXMuCgoKQWwgaWd1YWwgcXVlIGFudGVzLCBwb2RlbW9zIG9taXRpciBsb3Mgbm9tYnJlcyBkZSBsYXMgb3BjaW9uZXMgZGUgbGEgZnVuY2nDs24gYGFlcygpYC4gRXN0YSBmdW5jacOzbiBwdWVkZSB0ZW5lciBtdWNob3MgYXJndW1lbnRvcyAodmVyZW1vcyBhbGd1bm9zKSwgcGVybyBsb3MgMiBwcmltZXJvcyBzb24gc2llbXByZSB4IChlbCBlamUgeCkgeSBkZXNwdcOpcyB5IChlbCBlamUgeSBvIHZlcnRpY2FsIGRlIG1pIGdyw6FmaWNvKTsgZXMgZGVjaXIgcG9kcsOtYW1vcyBoYWNlciBsbyBzaWd1aWVudGUgcXVlIGVzIG3DoXMgcsOhcGlkbyBkZSB0ZWNsZWFyLgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKQpgYGAKCkNvbiBlc3RhIGluc3RydWNjacOzbiBsZSBlc3RhbW9zIGRpY2llbmRvIGEgYGdncGxvdDJgIHF1ZSB2YW1vcyBhIGhhY2VyIHVuIGdyw6FmaWNvIGNvbiBsb3MgZGF0b3MgZGVsIGRhdGEuZnJhbWUgYGlyaXNgIHkgcXVlIHZhbW9zIGEgYXNvY2lhci9jb25lY3Rhci9tYXBlYXIgbGEgdmFyaWFibGUgYFNlcGFsLkxlbmd0aGAgY29uIGVsIGVqZSB4LCB5IGxhIHZhcmlhYmxlIGBQZXRhbC5MZW5ndGhgIGNvbiBlbCBlamUgeSBkZWwgZ3LDoWZpY28uIFBlcmZlY3RvLCBwZXJvIGVudG9uY2VzIMK/cG9yIHF1w6kgbm8gdmVtb3MgZWwgZ3LDoWZpY28/IExhIHJhesOzbiBlc3RyaWJhIGVuIHF1ZSBubyBsZSBoZW1vcyBkaWNobyBhIGBnZ3Bsb3QyYCBxdcOpIHRpcG8gZGUgZ3LDoWZpY28gcXVlcmVtb3MgKGRlIHB1bnRvcywgZGUgbGluZWFzIGV0Yy4uLikuIEVsIHRpcG8gZGUgZ3LDoWZpY28gc2UgZXhwbGljaXRhIGNvbiB1bmEgZmFtaWxpYSBkZSBmdW5jaW9uZXM6IGBnZW9tX3h4KClgIG8gKipnZW9tKipldHLDrWFzLiBIYXkgbXVjaGFzIGdlb21ldHLDrWFzIG8gdGlwb3MgZGUgZ3LDoWZpY29zIHF1ZSBwb2RlbW9zIHVzYXIuIFlhIGxvIHZlcmVtb3MhISEgTm9zb3Ryb3MgcXVlcmVtb3MgaGFjZXIgdW4gZ3LDoWZpY28gZGUgcHVudG9zLCBhc8OtIHF1ZSwgZGUgbGEgZmFtaWxpYSBkZSAqKmdlb20qKmV0csOtYXMgdGVuZW1vcyBxdWUgIHVzYXIgYGdlb21fcG9pbnQoKWAuIFZlw6Ftb3Nsby4KCgpgYGB7ciwgb3V0LndpZHRoID0gIjUwJSJ9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKQpgYGAKCkNvbW8gdmVtb3MgbG9zIHB1bnRvcyBkZWwgZ3LDoWZpY28gc2UgdmlzdWFsaXphbiBlbiBjb2xvciBuZWdybywgY29uIHVuIHRhbWHDsW8sIHVuYSB0cmFuc3BhcmVuY2lhIHkgdW5hIGZvcm1hIGRldGVybWluYWRhcy4gT2J2aWFtZW50ZSB0b2RvIGVzdG8gc2UgcHVlZGUgY2FtYmlhciBkZW50cm8gZGUgYGdlb21fcG9pbnQoKWAgY29uIGxhcyBvcGNpb25lcyBhZGVjdWFkYXMuIFBvciBlamVtcGxvOgoKCmBgYHtyLCBvdXQud2lkdGggPSAiNTAlIn0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludChjb2xvciA9ICJyZWQiLCBzaXplID0gMiwgYWxwaGEgPSAwLjIpCmBgYAoKCgpZYSBjYXNpIGVzdMOhLiBObyBlcyB0YW4gY29tcGxpY2Fkby4gWWEgaGVtb3MgdmlzdG8gbGFzIGlkZWFzIGZ1bmRhbWVudGFsZXMgZGUgbGEgdmlzdWFsaXphY2nDs24gY29uIGBnZ3Bsb3QyYDoKCjEuIExvcyBncsOhZmljb3Mgc2UgaW5pY2lhbiBjb24gbGEgZnVuY2nDs24gYGdncGxvdCgpYC4gR2VuZXJhbG1lbnRlIGFxdcOtIHNlIGVzcGVjaWZpY2FuIGxvcyBkYXRvcyAoZGF0YS5mcmFtZSkgcXVlIHF1ZXJlbW9zIHV0aWxpemFyICAKCjIuIExhIGZ1bmNpw7NuIGBhZXMoKWAgc2lydmUgcGFyYSBhc29jaWFyL21hcHBlYXIgdmFyaWFibGVzIGNvbiBhdHJpYnV0b3MvY2FyYWN0ZXLDrXN0aWNhcyBlc3TDqXRpY2FzIGRlbCBncsOhZmljby4gRGUgaGVjaG8gZWwgbm9tYnJlIGRlbCBmdW5jacOzbiBgYWVzKClgIHZpZW5lIGRlICoqYWVzKip0aGV0aWNzLiBMYXMgKiphZXMqKnRoZXRpY3MgbcOhcyBpbXBvcnRhbnRlcyBkZSB1biBncsOhZmljbyBzdWVsZW4gc2VyIGxvcyBlamVzIHggZSB5LCBwb3IgZXNvIHNlIHBvbmVuIHNpZW1wcmUgYWwgcHJpbmNpcGlvIGRlIGBhZXMoKWA7IGVzIGRlY2lyLCBzb24gbG9zIDIgcHJpbWVyb3MgYXJndW1lbnRvcyBkZSBgYWVzKClgLiBFbiBudWVzdHJvIGVqZW1wbG8gaGVtb3MgYXNvY2lhZG8gbGEgdmFyaWFibGUgYFNlcGFsLkxlbmd0aGAgY29uIGVsIGVqZSB4LCB5IGxhIHZhcmlhYmxlIGBQZXRhbC5MZW5ndGhgIGNvbiBlbCBlamUgeS4gVmVyZW1vcyBtw6FzICoqYWVzKip0aGV0aWNzLCBjb21vIHBvciBlamVtcGxvIGVsIGNvbG9yIG8gZWwgdGFtYcOxbyAoZGUgbG9zIHB1bnRvcyAuLi4gbyBkZSBsYXMgbGluZWFzIG8gLi4uKQoKMy4gQ29uIGBnZW9tXyoqKClgIGVsZWdpbW9zIGVsIHRpcG8gbyAqKmdlb20qKmV0csOtYSBkZSBncsOhZmljby4gSGF5IG11Y2hvcyB0aXBvcyBkZSBncsOhZmljb3MsIGFzw60gcXVlIGhhYnLDoW4gbXVjaG9zICoqZ2VvbXMqKi4gUG9yIGVqZW1wbG86IGdlb21fcG9pbnQoKSwgZ2VvbV9saW5lKCksIGdlb21fIC4uLgoKCkVzdGFzIDMgaWRlYXMgc29uIGxhcyBwcmluY2lwYWxlcyBwYXJhIGVudGVuZGVyIGdncGxvdDIuIERlc3B1w6lzIGhheSBtdWNoYXMgbcOhcyBvcGNpb25lcyB5IGVsZW1lbnRvcyBxdWUgc2Vyw6FuIG11eSBpbXBvcnRhbnRlcyBwYXJhIGNvbnNlZ3VpciB1biBidWVuIGdyw6FmaWNvLCBwZXJvIGVuIGNpZXJ0YSBmb3JtYSBzb24gc2VjdW5kYXJpYXM7IHBvciBlamVtcGxvLCBsb3MgdMOtdHVsb3MsIGxvcyBlamVzLCBsYXMgZXNjYWxhcywgZWwgdGVtYSBldGMuLi4gbG8gaXJlbW9zIHZpZW5kbyBwb2NvIGEgcG9jby4KCjxicj4KCkFmaWFuY2Vtb3MgbGFzIGlkZWFzIHByaW5jaXBhbGVzIGRlIGxhIHZpc3VhbGl6YWNpw7NuIGNvbiBgZ2dwbG90MmAuIMK/UGllbnNhIHF1ZSBoYXLDoSBsYSBzaWd1aWVudGUgbGluZWEgZGUgY8OzZGlnbz8KCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2xpbmUoKQpgYGAKCkxhIHByaW1lcmEgcGFydGUgZGUgbGEgaW5zdHJ1Y2Npw7NuIGVzIGlndWFsIGEgbGEgYW50ZXJpb3I6IHF1ZXJlbW9zIHVuIGdyw6FmaWNvIGNvbiBlbCBkYXRhLmZyYW1lIGlyaXMgeSBxdWVyZW1vcyBhc29jaWFyIGxhIHZhcmlhYmxlIFNlcGFsLkxlbmd0aCBjb24gZWwgZWplIHgsIGNvbiBsYSBwcm9waWVkYWQgKiphZXMqKnRoZXRpYyBlamUgeCB5IFBldGFsLkxlbmd0aCBjb24gZWwgZWplIHk7IHBlcm8gbGUgaGVtb3MgcGVkaWRvIHVuIGdyw6FmaWNvIGRlIGxpbmVhcyAoZ2VvbV9saW5lKCkpLiBFbiBlc3RlIGNhc28gaGFjZXIgdW4gZ3LDoWZpY28gZGUgbGluZWFzIG5vIHRpZW5lIG11Y2hvIHNlbnRpZG8sIHBlcm8gc2kgc2UgbG8gcGVkaW1vcyBhIFIsIGVzdGUgbm9zIGhhY2UgY2FzbyB5IG5vcyBsbyBtdWVzdHJhLgoKCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI0OSUifQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2xpbmUoKQpgYGAKCjxicj4KCllhIGRpamUgcXVlIHVuYSBkZSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBpbXBvcnRhbnRlcyBkZSBgZ2dwbG90MmAgIGVzIHF1ZSBmdW5jaW9uYSBwb3IgY2FwYXMgcXVlIHNlIHZhbiBzdXBlcnBvbmllbmRvOyBwYXJhIGlyIGHDsWFkaWVuZG8gY2FwYXMgYSBudWVzdHJvIGdyw6FmaWNvIHRlbmVtb3MgcXVlIHVzYXIgZWwgc8OtbWJvbG8gKipgK2AqKi4gUG9yIGVqZW1wbG8gc2kgcXVpc2nDqXJhbW9zIHZlciBsb3MgcHVudG9zIHkgbGFzIGxpbmVhcyDCv0PDs21vIGxvIGhhY2Vtb3M/CgoKYGBge3IsIG91dC53aWR0aCA9ICI0OSUifQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKQpgYGAKCjxicj4KClRhbXBvY28gc29uIG11eSDDunRpbGVzIGxhIGxpbmVhcyBlbiBlc3RlIGdyw6FmaWNvLgoKCgpPdHJhIGdlb21ldHLDrWEgbyAqKmdlb21fKCkqKiBxdWUgc2UgdXNhIG11Y2hvIGVzIGBnZW9tX3Ntb290aCgpYC4gUHJvYsOpbW9zbGE6CgoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSArICBnZW9tX3Ntb290aCgpCmBgYAoKRGVsIGdyw6FmaWNvIHB1ZWRlIGluZmVyaXJzZSBxdWUgaGF5IHVuYSByZWxhY2nDs24sIG5vIGxpbmVhbCwgcGVybyBzw60gZGlyZWN0YSBvIHBvc2l0aXZhIGVudHJlIGxhIGxvbmdpdHVkIGRlbCBzw6lwYWxvIHkgZGVsIHDDqXRhbG87IHBlcm8gdGFtYmnDqW4gc2UgYXByZWNpYSBxdWUgaGF5IGFsIG1lbm9zIGRvcyBncnVwb3MgZGlzdGludG9zIGRlIGxpcmlvcy4gSGF5IHVuIGdydXBvIGRlIG9ic2VydmFjaW9uZXMgY3V5byBww6l0YWxvIHBhcmVjZSBzZXIgY2xhcmFtZW50ZSBtZW5vciBxdWUgZWwgZGVsIHJlc3RvIGRlIGxpcmlvcy4gVmVhbW9zIHNpIGVzdG8gc2UgZGViZSBvIGVzdGEgYXNvY2lhZG8gYWwgdGlwbyBkZSBsaXJpbywgcmVjdWVyZGEgcXVlIGhheSAzIHRpcG9zIGRlIGxpcmlvcywgYXNvY2lhZG9zIGEgbGEgdmFyaWFibGUgYGlyaXMkU3BlY2llc2AuIFBhcmEgdmVybG8gZW4gbnVlc3RybyBncsOhZmljbyBsbyBxdWUgdmFtb3MgYSBoYWNlciBlcyBhc29jaWFyL21hcGVhciBsYSB2YXJpYWJsZSBgU3BlY2llc2AgY29uIGxhICoqYWVzKip0aGV0aWNzIGNvbG9yOgoKCgpgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KCkgCmBgYAoKUHVlcyBwYXJlY2UgcXVlIHPDrSwgcXVlIGxhIGVzcGVjaWUgZGUgbGlyaW9zICJzZXRvc2EiIGVzIG3DoXMgcGVxdWXDsWEsIGFsIG1lbm9zIGVuIGxvbmdpdHVkLCBkZWwgc8OpcGFsbywgcGVybyBzb2JyZSB0b2RvIGRlbCBww6l0YWxvLgoKTGEgdmFyaWFibGUgYFNwZWNpZXNgIHBvZHLDrWFtb3MgaGFiZXJsYSBhc29jaWFkbyBhIGxhIGVzdMOpdGljYSB0YW1hw7FvIChzaXplKSwgbyBhIGxhIGVzdMOpdGljYSBmb3JtYSAoc2hhcGUpIHBlcm8gbm8gc2Vyw61hIHRhbiDDunRpbCBuaSBxdWVkYXLDrWEgdGFuIGJvbml0byBlbCBncsOhZmljby4gRsOtamF0ZSBxdWUgaW5jbHVzbyBSIG5vcyBhdmlzYSBkZSBxdWUgYXNvY2lhciBsYSBwcm9waWVkYWQgbyAqKmFlcyoqdGhldGljIHRhbWHDsW8gY29uIHVuYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSBjb21vIFNwZWNpZXMgbm8gZXMgbXV5IHJlY29tZW5kYWJsZS4KCgpgYGB7ciwgd2FybmluZyA9IFRSVUV9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIHNpemUgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KCkgCmBgYAoKVGFtYmnDqW4gcG9kZW1vcyBhc29jaWFyIGxhIHZhcmlhYmxlIGBTcGVjaWVzYCBhIGxhIGVzdMOpdGljYSAiZm9ybWEiKHNoYXBlKS4gTG8gaGFjZW1vcyBlbiBsYSBleHByZXNpw7NuIGRlIG3DoXMgYWJham8uIENvbW8gdmVpcywgYWhvcmEgbGFzIGRpZmVyZW5jaWFzIGVudHJlIGVzcGVjaWVzIGRlIGxpcmlvcyBubyBzZSBhcHJlY2lhbiB0YW4gYmllbiBjb21vIGN1YW5kbyB1c8OhYmFtb3MgYGNvbG9yID0gU3BlY2llc2AKCgpgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgc2hhcGUgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KCkgCmBgYAoKCk9zIHZhIGEgY29zdGFyIGhhY2VyIGdyw6FmaWNvcywgbm9ybWFsISEhLCBwZXJvIGVzcGVybyBxdWUgbGEgaWRlYSBwcmluY2lwYWwgeWEgbGEgdGVuZ8OhaXMuIExvIHF1ZSBwYXNhIGVzIHF1ZSBubyBvcyBoZSBjb250YWRvIHRvZG8sIGVuIHJlYWxpZGFkIGVzIHVuIHBvY28gbcOhcyBjb21wbGVqbyB5IHZlcnPDoXRpbC4gTG8gbWVkaW8gZXhwbGljbyBlbiBlbCBzaWd1aWVudGUgYXBhcnRhZG8uCgoKPGJyPgoKIyMjICBNw6FzIGlkZWFzIHNvYnJlIGdncGxvdDIKCllhIHRlbsOpaXMgbGFzIGlkZWFzIHByaW5jaXBhbGVzIHBhcmEgaGFjZXIgZ3LDoWZpY29zIGNvbiBgZ2dwbG90MmAsIHBlcm8gbm8gb3MgbG8gaGUgY29udGFkbyB0b2RvLCB0YW1wb2NvIGxvIHZveSBhIGhhY2VyIGFob3JhLCBwZXJvIHNpIGNvbnRhcsOpIGxhcyBjb3NhcyBkZSB1bmEgZm9ybWEgZGlmZXJlbnRlIHBhcmEgcXVlIHRlbmfDoWlzIG3DoXMgZmxleGliaWxpZGFkL3ZlcnNhdGlsaWRhZCBhIGxhIGhvcmEgZGUgaGFjZXIgZ3LDoWZpY29zLiBTaSBxdWVyw6lpcyBzYWJlciB0b2RhIGxhIHZlcmRhZF5bU2kgYWxndW5vIGRlIHZvc290cm9zIG1lIHByZWd1bnRhIHBvciBsYSB2ZXJkYWQgYWRvcHRhcsOpIHNlbWJsYW50ZSBoaWVyw6F0aWNvIHkgc8OzbG8gZGlyw6kgbG8gc2lndWllbnRlOiBnZW9tX3BvaW50KCkgaXMgYSBzaG9ydC1oYW5kIGZvciBsYXllcihnZW9tID0gInBvaW50Iiwgc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImlkZW50aXR5Iikgw7MgYXBlbGFyw6kgYSBsYSBjYW5jacOzbiBkZSBBbGJlcnQgUGxhLCBzZWfDum4gbWUgZGUuXSB0ZW5kcsOpaXMgcXVlIGlyIGFsIGxpYnJvIGRlIEhhZGxleSwgY29uY3JldGFtZW50ZSBbYXF1w61dKGh0dHBzOi8vZ2dwbG90Mi1ib29rLm9yZy9sYXllcnMuaHRtbCkuCgpMYSBmb3JtYSBxdWUgb3MgaGUgY29udGFkbyAgZGUgaGFjZXIgZ3LDoWZpY29zIGBnZ3Bsb3QyYCBlcyBsYSBxdWUgdmVyw6lpcyBoYWJpdHVhbG1lbnRlLCB5byB0YW1iacOpbiBoYWdvIG1pcyBncsOhZmljb3MgYXPDrSwgc29sbyBxdWUgcGFyYSBlbnRlbmRlciBtZWpvciBlbCBmdW5jaW9uYW1pZW50bywgbGEgc2ludGF4aXMgZGUgYGdncGxvdDJgLCBvcyBsbyB0ZW5nbyBxdWUgY29udGFyIG90cmEgdmV6IGRlIHVuYSBmb3JtYSB1biBwb2NvIGRpZmVyZW50ZSBvIGFtcGxpYWRhLiAKClVuIGdyw6FmaWNvIGRlIGdncGxvdDIgc2UgaW5pY2lhIGxsYW1hbmRvIGEgbGEgZnVuY2nDs24gYGdncGxvdCgpYCBlc28gZXMgY2llcnRvIHkgdGFtYmnDqW4gZXMgdmVyZGFkIHF1ZSBnZW5lcmFsbWVudGUgZGVudHJvIGRlIGBnZ3Bsb3QoKWAgc2UgaW5kaWNhIGVsIGRhdGEuZnJhbWUgcXVlIHZhcyBhIHV0aWxpemFyIHkgY29uIGBhZXMoKWAgcXVlIHZhcmlhYmxlcyB2YXMgYSB1c2FyIHkgY29uIHF1ZSBlbGVtZW50b3MgdmlzdWFsZXMgbyBlc3TDqXRpY29zIHF1aWVyZXMgYXNvY2lhciBjYWRhIHVuYSBkZSBsYSB2YXJpYWJsZXMgcXVlIHZhcyBhIHV0aWxpemFyXltFbiBgYWVzKClgIG5vIHPDs2xvIHB1ZWRlcyBwb25lciB2YXJpYWJsZXMsIHNpbm8gdHJhbnNmb3JtYWNpb25lcyBkZSBlbGxhcywgcG9yIGVqZW1wbG8gYWVzKHggPSB2MSBeIDIsIHkgPSB2MSAvIHYyKS4gVGFtYmnDqW4gc2UgcHVlZGVuIG1hcHBlYXIgdmFyaWFibGVzIGNvbiBjb25zdGFudGVzIGFlcyh4ID0gMSwgY29sb3VyID0gImxvcXVlc2VhIildLiBDb3JyZWN0bywgcGVybyAuLi4uIGVuIHJlYWxpZGFkIHVuIGdyw6FmaWNvIGdncGxvdDIgc2UgaGFjZSBwb3IgY2FwYXMsIGNhZGEgY2FwYSBzZSBlc3BlY2lmaWNhIGNvbiB1bmEgZnVuY2nDs24gZGUgbGEgZmFtaWxpYSBgZ2VvbV94eCgpYCwgYXPDrSBxdWUgZW4gcmVhbGlkYWQgbG9zIGRhdG9zIHkgbGFzIGBhZXMoKWAgc2UgImRlYmVyw61hbiIgZXNwZWNpZmljYXIgZGVudHJvIGRlIGxhIGZ1bmNpw7NuIGdlb21feHgoKWAuIAoKUGFyZWNlIHVuIHBvY28gZGUgbMOtbyBwZXJvIGVuIGN1YW50byBsbyBlbnRpZW5kYXMgZXMgbXV5IGbDoWNpbCB5IHRlIHB1ZWRlIGRhciBtw6FzIGZsZXhpYmlsaWRhZCBhIGxhIGhvcmEgZGUgaGFjZXIgdHVzIGdyw6FmaWNvcy4gRW1wZWNlbW9zOiDCv3JlY3VlcmRhcyBxdWUgaGFjZW4gbGFzIGxpbmVhcy9leHByZXNpb25lcyBkZSBhYmFqbz8KCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX3BvaW50KCkgCmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZSgpCmBgYAoKUG9kZW1vcyBwZW5zYXIgcXVlIGxhcyBmdW5jaW9uZXMgcXVlIGhhY2VuIGxhIHJlcHJlc2VudGFjacOzbiBncsOhZmljYSByZWFsbWVudGUgc29uIGxhcyBgZ2VvbV94eCgpYDsgZXMgYWjDrSBkb25kZSBkZWJlcsOtYW1vcyBlc3BlY2lmaWNhciBsb3MgZGF0b3MgeSB2YXJpYWJsZXMvZXN0w6l0aWNhcyBxdWUgcXVlcmVtb3MgdXNhciwgcGVybyBzaSBubyBsYXMgZXNwZWNpZmljYW1vcyBlbiBsYSBmdW5jacOzbiBgZ2VvbSgpYCwgZW50b25jZXMsIGdncGxvdDIgbWlyYXLDoSBhIHZlciBzaSBleGlzdGVuLCBzaSBlc3TDoW4gZXNwZWNpZmljYWRvcywgZGVudHJvIGRlIGBnZ3Bsb3QoKWAuCgoKQWhvcmEgcXVlIHlhIHNhYmVtb3MgZWwgZnVuY2lvbmFtaWVudG8gYsOhc2ljbyBkZSBgZ2dwbG90MmAsIHZlYW1vcyBhbGd1bm9zIGRldGFsbGVzIG1lZGlhbnRlIGFsZ3Vub3MgZWplbXBsb3MuIEhhc3RhIGFob3JhIGhlbW9zIGVzcGVjaWZpY2FkbyBlbCBkYXRhLmZyYW1lIHF1ZSBxdWVyZW1vcyBncmFmaWNhciBjb24gYGdncGxvdChkYXRhID0gbXlfZGYpYCBvIGNvbiBgZ2dwbG90KG15X2RmKWAgeSBsYXMgdmFyaWFibGVzIHF1ZSBxdWVyZW1vcyB2ZXIsIHkgYSBxdWUgcHJvcGllZGFkIGVzdMOpdGljYSBxdWVyZW1vcyBhc29jaWFybGEsIGNvbiBsYSBmdW5jacOzbiBgYWVzKClgICoqZGVudHJvIGRlIGBnZ3Bsb3QoKWAqKi4gU2kgbG8gaGFjZW1vcyBhc8OtLCB0b2RvcyBsb3MgYGdlb21zX3h4KClgIHF1ZSB1dGlsaWNlbW9zIGNvbXBhcnRpcsOhbiBlbCBjb25qdW50byBkZSBkYXRvcyB5IGxhcyB2YXJpYWJsZXMvZXN0w6l0aWNhcyBhIG1hcHBlYXIgeSBtb3N0cmFyOyBwZXJvIGEgdmVjZXMsIGVuIGdyw6FmaWNvcyBtw6FzIGNvbXBsZWpvcyBwb2RlbW9zIHF1ZXJlciBoYWNlciBxdWUgY2FkYSBgZ2VvbV94eCgpYCBtdWVzdHJlIGRhdG9zIHkvbyB2YXJpYWJsZXMgZGlzdGludGFzLiAKCkVudGVuZGVyIHF1ZSBjYWRhIGBnZW9tX3h4KClgIHB1ZWRlIGVzdGFyIGFzb2NpYWRvIGEgZGlzdGludG9zIGRhdGEuZnJhbWVzIHkvbyB2YXJpYWJsZXMgZXMgaW1wb3J0YW50ZSBwYXJhIHRlbmVyIG3DoXMgdmVyc2F0aWxpZGFkIGNvbiBgZ2dwbG90MmAuUG9yIGVqZW1wbG8sIGxhcyBzaWd1aWVudGVzIHRyZXMgZXhwcmVzaW9uZXMgaGFjZW4gZWwgbWlzbW8gZ3LDoWZpY28uIFNlIHN1ZWxlIHV0aWxpemFyIGxhIHByaW1lcmEgZXhwcmVzacOzbiwgcGVybyBsYSBzZWd1bmRhIHkgdGVyY2VyYSBleHByZXNpb25lcyBzb24gIm3DoXMgZmxleGlibGVzIiwgYXVucXVlIGVzIHZlcmRhZCBxdWUgc2kgc8OzbG8gc2UgdXRpbGl6YSB1biBgZ2VvbV94eCgpYCBubyBnYW5hbW9zIG5hZGEgcG9yIHVzYXIgbGEgc2VndW5kYSBvIHRlcmNlcmEgZXhwcmVzacOzbiwgcGVybyBubyBzZXLDoSBlbCBjYXNvIHNpIGVuIG51ZXN0cm8gZ3LDoWZpY28gbmVjZXNpdGFtb3MgdXNhciB2YXJpb3MgYGdlb21feHgoKWAKCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpCgpnZ3Bsb3QoaXJpcykgKyBnZW9tX3BvaW50KGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpCgpnZ3Bsb3QoKSArIGdlb21fcG9pbnQoZGF0YSA9IGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpCmBgYAoKRsOtamF0ZSwgY29tbyBkZXRhbGxlLCBwZXJvIGltcG9ydGFudGUsIHF1ZSBzaSB1dGlsaXphcyBsYSB0ZXJjZXJhIGV4cHJlc2nDs247IGVzIGRlY2lyLCBzaSBlc3BlY2lmaWNhcyBsb3MgZGF0b3MgZGVudHJvIGRlIGxhIGZ1bmNpw7NuIGBnZW9tX3h4KClgLCBlcyBuZWNlc2FyaW8gcG9uZXIgZWwgbm9tYnJlIGRlbCBhcmd1bWVudG8gOyBlcyBkZWNpciwgZGViZXMgcG9uZXIgYGRhdGEgPSBpcmlzYCwgbm8gcHVlZGVzIHBvbmVyIHNvbG8gYGlyaXNgLiBZbyBtZSBvbHZpZG8gc2llbXByZSBkZSBlc3RlIGRldGFsbGVeW0VuIGVzdGUgY2FzbyBzw60gcXVpZXJvIHF1ZSBtZSBwcmVndW50w6lpcyBwb3IgZXN0ZSBkZXRhbGxlLiBFcyBhbGdvIHF1ZSB5YSBkZWJlcsOtYXMgc2FiZXIvaW50dWlyIHBlcm8gLi4uXS4KCmBgYHtyLCBlY2hvID0gRkFMU0V9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKQpgYGAKCgoKRW4gZXN0ZSBjYXNvIChjb21vIGVsIGdyw6FmaWNvIHNvbG8gdGllbmUgdW5hIGNhcGEsIGNvbW8gc8OzbG8gdXNhbW9zIHVuIGBnZW9tKF94eClgKSBubyBnYW5hbW9zIG5hZGEgcG9yIHVzYXIgbGEgc2VndW5kYSBvIHRlcmNlcmEgZXhwcmVzacOzbjsgUEVSTywgY3VhbmRvIHVzZW1vcyB2YXJpb3MgYGdlb21feHgoKWAgZXN0byBub3MgZGFyw6EgbXVjaGFzIHBvc2liaWxpZGFkZXMgcGFyYSBudWVzdHJvIGdyw6FmaWNvLiAKCkludGVudGEgZGVzY3VicmlyIGxhcyBkaWZlcmVuY2lhcyB5IGZ1bmNpb25hbWllbnRvIGRlIGxhcyAzIHNpZ3VpZW50ZXMgaW5zdHJ1Y2Npb25lcy4gUmVjdWVyZGEgcXVlIHB1ZWRlcyBjb3JyZXIgbGFzIGluc3RydWNjaW9uZXMgZW4gUiBwYXJhIHZlciBxdWUgaGFjZW4gZXhhY3RhbWVudGUuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkKZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3Ntb290aCgpCmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IFNwZWNpZXMpKQpgYGAKCgpWZcOhbW9zbGFzIHVuYSBhIHVuYToKCjEuIExvcyBkYXRvcyB5IGxhcyAzIHZhcmlhYmxlcy9lc3TDqXRpY2FzIGRlbnRybyBkZSBnZ3Bsb3QoKQoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llcykpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoKQpgYGAKCkVuIGVzdGUgY2FzbyBsb3MgMiBnZW9tcyBjb21wYXJ0ZW4gZWwgY29uanVudG8gZGUgZGF0b3MgKGlyaXMpIHkgbGFzIHZhcmlhYmxlcy9lc3TDqXRpY2FzIGEgZ3JhZmljYXIKCjxicj4KCjIuIExvcyBkYXRvcyB5IDIgdmFyaWFibGVzL2VzdMOpdGljYXMgZGVudHJvIGRlIGdncGxvdCgpLCBwZXJvIHVuYSB0ZXJjZXJhIHZhcmlhYmxlIChTcGVjaWVzKSwgYXNvY2lhZGEgYSBsYSBlc3TDqXRpY2EgY29sb3IsIGFwYXJlY2Ugc29sYW1lbnRlIGVuIGBnZW9tX3BvaW50KClgCgoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gU3BlY2llcykpICsgZ2VvbV9zbW9vdGgoKQpgYGAKCjxicj4KCjMuIExvcyBkYXRvcyB5IDIgdmFyaWFibGVzL2VzdMOpdGljYXMgZGVudHJvIGRlIGdncGxvdCgpLCBwZXJvIHVuYSB0ZXJjZXJhIHZhcmlhYmxlIChTcGVjaWVzKSwgYXNvY2lhZGEgYSBsYSBlc3TDqXRpY2EgY29sb3IsIGFwYXJlY2Ugc29sYW1lbnRlIGVuIGBnZW9tX3Ntb290aCgpYAoKCgpgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gU3BlY2llcykpCmBgYAoKRXNwZXJvLCBzZWd1cm8hISwgcXVlIHRlIGhhcyBkYWRvIGN1ZW50YSBkZSBxdWUgc2kgZXNwZWNpZmljYXMgZWwgZGF0YS5mcmFtZSB5IGxhcyB2YXJpYWJsZXMvZXN0w6l0aWNhcyBkZW50cm8gZGUgYGdncGxvdCgpYCBlc3RvIGFmZWN0YXLDoSBhIHRvZG9zIGxvcyBnZW9tcyBkZWwgZ3LDoWZpY287IHBlcm8gbG8gcXVlIHNlIGVzcGVjaWZpcXVlIGRlbnRybyBkZSB1biBgZ2VvbV94eCgpYCBzb2xvIGFmZWN0YSBhIGVzYSBnZW9tZXRyw61hLgoKPGJyPgoKKipPdHJvIGVqZW1wbG8qKiBwYXJhIGVudGVuZGVybG8sIMK/cG9yIHF1w6kgbm8gZnVuY2lvbmEgbGEgc2lndWllbnRlIGV4cHJlc2nDs24/CgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBtZXNzYWdlID0gVFJVRX0KZ2dwbG90KGlyaXMpICsgZ2VvbV9wb2ludChhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IFNwZWNpZXMpKQpgYGAKClB1ZXMgcG9ycXVlIHBhcmEgcG9kZXIgcmVwcmVzZW50YXIgbGEgbGluZWEgc3Vhdml6YWRhIHNlIHV0aWxpemEgYGdlb21fc21vb3RoKClgLCB5IGBnZW9tX3Ntb290aCgpYCBuZWNlc2l0YSBjb21vIG3DrW5pbW8gdGVuZXIgdmFyaWFibGVzIGFzb2NpYWRhcyBhIGxhcyBlc3TDqXRpY2FzIHggZSB5LiBDb21vIHZlaXMsIGRlbnRybyBkZSBgZ2VvbV9zbW9vdGgoKWAgc29sbyBoZW1vcyBlc3BlY2lmaWNhZG8gbGEgZXN0w6l0aWNhICJjb2xvciIgeSB0YW1wb2NvIGhlbW9zIGVzcGVjaWZpY2FkbyBlbiBsYSBmdW5jacOzbiBgZ2dwbG90KClgIHF1ZSB2YXJpYWJsZXMgc2UgYXNvY2lhbiBjb24geCBlIHkuIFBvciBsbyB0YW50bywgYGdlb21fc21vb3RoKClgIG5vIHB1ZWRlIGhhY2VyIHN1IHRyYWJham8sIGxlIGZhbHRhbiBsb3MgImRhdG9zIiBkZSB4IGUgeSBwYXJhIGNhbGN1bGFyL29idGVuZXIgbGEgbGluZWEgc3Vhdml6YWRhLiAKClZhbW9zIGNvbiAqKm90cm9zIGVqZW1wbG9zKiouIExhIHNpZ3VpZW50ZXMgZXhwcmVzaW9uZXMgdGFtcG9jbyBmdW5jaW9uYXLDoW5eW0VuIHJlYWxpZGFkIGxhIHRlcmNlcmEgZXhwcmVzacOzbiBzw60gY29ycmVyw6EsIHBlcm8gc8OzbG8gbW9zdHJhcsOhIGxvcyBwdW50b3MsIG5vIGxhcyBsaW5lYXMuXSBzaSBpbnRlbnTDoWlzIGNvcnJlcmxhcyBlbiB2dWVzdHJvIG9yZGVuYWRvci4gwr9Qb3IgcXXDqT8KCgoKYGBge3IsIGV2YWwgPSBGQUxTRSwgbWVzc2FnZSA9IFRSVUV9CmdncGxvdCgpICsgZ2VvbV9wb2ludChkYXRhID0gaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2xpbmUoYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkKCmdncGxvdChhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoZGF0YSA9IGlyaXMpICsgZ2VvbV9saW5lKCkKCmdncGxvdCgpICsgZ2VvbV9wb2ludChkYXRhID0gaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2xpbmUoKQpgYGAKCjxicj4KCioqT3RybyBlamVtcGxvOioqIGhhZ2Ftb3MgYWxnbyBtw6FzIG1hcmNpYW5vL2NvbXBsaWNhZG8uIFN1cMOzbiBxdWUgcXVpZXJlcyBoYWNlciB1biBncsOhZmljbyBkaWZlcmVuY2lhbmRvIGxvcyBwdW50b3MgcG9yIGNvbG9yIHBhcmEgbGFzIHRyZXMgZXNwZWNpZXMgZGUgbGlyaW9zLCBwZXJvIHF1aWVyZXMgcXVlIHNvbG8gc2UgdmVhIGxhIGxpbmVhIHN1YXZpemFkYSBwYXJhIGxhcyBkb3MgZXNwZWNpZXMgbcOhcyBncmFuZGVzICh2aXJnaW5pY2EgeSB2ZXJzaWNvbG9yKS4gTG9zIGxpcmlvcyBtw6FzIHBlcXVlw7FvcyBzb24gbG9zIGRlIGxhIGNsYXNlIHNldG9zYS4gSWd1YWwgc2UgcHVlZGUgaGFjZXIgZGUgb3RyYSBmb3JtYSBwZXJvIGxhIHF1ZSBtZSB2aWVuZSBhIGxhIGNhYmV6YSBlcyBoYWNlciBsbyBzaWd1aWVudGU6CgoKUHJpbWVybywgY3JlYXIgdW4gZGF0YXNldCBxdWUgc8OzbG8gY29udGVuZ2EgYSBsb3MgbGlyaW9zIGdyYW5kZXMsIGxvcyBkZSBsYXMgZXNwZWNpZXMgdmlyZ2luaWNhIHkgdmVyc2ljb2xvci4KCmBgYHtyfQppcmlzMiA8LSBpcmlzICU+JSBmaWx0ZXIoU3BlY2llcyAhPSAic2V0b3NhIikgIy0gbWUgcXVlZG8gY29uIGxvcyBsaXJpb3MgcXVlIG5vIHNvbiBkZSBjbGFzZSAic2V0b3NhIgpgYGAKClBhcmEgZGVzcHXDqXMgaGFjZXIgZWwgZ3LDoWZpY28gY29uIGN1YWxxdWllcmEgZGUgbGFzIDIgZXhwcmVzaW9uZXMgc2lndWllbnRlcy4gUHJlZmllcm8gbGEgc2VndW5kYSBwb3JxdWUgaGF5IHF1ZSB0ZWNsZWFyL2VzY3JpYmlyIG1lbm9zLCBwZXJvIHB1ZWRlIHF1ZSBzZWEgbcOhcyBkaWTDoWN0aWNhIGxhIHByaW1lcmEuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoKSArIGdlb21fcG9pbnQoZGF0YSA9IGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3Ntb290aChkYXRhID0gaXJpczIsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkgKSAKCmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gU3BlY2llcykpICsgZ2VvbV9zbW9vdGgoZGF0YSA9IGlyaXMyKQpgYGAKCjxicj4KCioqT3RybyBlamVtcGxvKiogbcOhczogwr95IHNpIHF1aXNpw6lyYW1vcyBxdWUgbGFzIDIgZXNwZWNpZXMgZ3JhbmRlcyBzZSByZXByZXNlbnRlbiBjb24gZWwgbWlzbW8gY29sb3I/IEhheSB2YXJpYXMgc29sdWNpb25lcywgdW5hIGRlIGxhcyBtw6FzIG1hcmNpYW5hcyBlcyBsYSBxdWUgcHJvcG9uZ28gYWJham8uIEVzIHVuYSBzb2x1Y2nDs24gcmFyYSwgcGVybyBjcmVvIHF1ZSBvcyBheXVkYXLDoSBhIGVudGVuZGVyIGBnZ3Bsb3QyYAoKUHJpbWVybyB2b3kgYSBjcmVhciB1biBudWV2byBkYXRhLmZyYW1lIHPDs2xvIGNvbiBsYXMgb2JzZXJ2YWNpb25lcyBkZSBsb3MgbGlyaW9zIHBlcXVlw7FvcywgbG9zIGRlIGxhIGNsYXNlIHNldG9zYS4KCmBgYHtyfQppcmlzX3NldG9zYSA8LSBpcmlzICU+JSBmaWx0ZXIoU3BlY2llcyA9PSAic2V0b3NhIikgIy0gbWUgcXVlZG8gY29uIGxvcyBsaXJpb3MgcGVxdWXDsW9zLCBsb3MgZGUgY2xhc2UgInNldG9zYSIKYGBgCgoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fcG9pbnQoZGF0YSA9IGlyaXNfc2V0b3NhLCBhZXMoY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3Ntb290aChkYXRhID0gaXJpczIsYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSApCmBgYAoKT3RyYSBzb2x1Y2nDs24sIHF1aXrDoXMgbcOhcyBsw7NnaWNhLCBjb25zaXN0ZSBlbiBwcmltZXJvIGFncnVwYXIgbGFzIDIgZXNwZWNpZXMgZGUgbGlyaW9zIGdyYW5kZXMgKHZlcnNpY29sb3IgeSB2aXJnaW5pY2EpIGVuIHVuYSBzb2xhIGNsYXNlLgoKCmBgYHtyfQppcmlzX3NvbG9fMl9jbGFzZXMgPC0gaXJpcyAlPiUgbXV0YXRlKFNwZWNpZXNfMiA9IGlmZWxzZShTcGVjaWVzICVpbiUgYygidmVyc2ljb2xvciIsICJ2aXJnaW5pY2EiKSwgInZlcnNpX3ZpcmdpIiwgInNldG9zYSIpKQpgYGAKClBhcmEgZGVzcHXDqXMgaGFjZXIgZWwgZ3LDoWZpY28uIEFkZW3DoXMgZWwgZ3LDoWZpY28gbG8gcG9kZW1vcyBoYWNlciBhbCBtZW5vcyBkZSAyIG1hbmVyYXMsIGxhIHNlZ3VuZGEgbXVjaG8gbWVqb3IsIGxhIHByaW1lcmEgZXhwcmVzacOzbiBlcyB1biBwb2NvIGVucmV2ZXNhZGE6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3BvaW50KGRhdGEgPSBpcmlzX3NldG9zYSwgYWVzKGNvbG9yID0gU3BlY2llcykpICsgZ2VvbV9zbW9vdGgoZGF0YSA9IGlyaXNfc29sb18yX2NsYXNlcyxhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llc18yKSApCgpnZ3Bsb3QoaXJpc19zb2xvXzJfY2xhc2VzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llc18yKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aCgpCmBgYAoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmdncGxvdChpcmlzX3NvbG9fMl9jbGFzZXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzXzIpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkKYGBgCgoKQ29tbyB2ZWlzLCBlbiBgZ2dwbG90MmAgaGF5IHZhcmlhcyBtYW5lcmFzIGRlIGhhY2VyIGVsIG1pc21vIGdyw6FmaWNvLiBFc3RvIGFsIHByaW5jaXBpbyBwdWVkZSBhYnJ1bWFyL21vbGVzdGFyLCBwZXJvIG11ZXN0cmEgbGEgZmxleGliaWxpZGFkIGRlIGxhIHNpbnRheGlzLgoKClBhcmEgaXIgYWNhYmFuZG8gY29uIGxhICJmaWxvc29mw61hIi9zaW50YXhpcy9ncmFtw6F0aWNhIGRlIGBnZ3Bsb3QyYCBpbnRlbnRhIGltYWdpbmFyIHF1ZSBncsOhZmljb3MgaGFjZW4gbGFzIDYgZXhwcmVzaW9uZXMgZGUgbcOhcyBhYmFqby4KClNpIG5vIHB1ZWRlcywgcmVjdWVyZGEgcXVlIHNpZW1wcmUgcHVlZGVzIGVqZWN1dGFyIGxhcyBvcmRlbmVzIGVuIGVsIG9yZGVuYWRvci4gRsOtamF0ZSBzb2JyZSB0b2RvIGVuIGxhIHRlcmNlcmEgZXhwcmVzacOzbgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoKQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkKZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KGNvbG9yID0gInB1cnBsZSIpICsgZ2VvbV9zbW9vdGgoKQoKZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChjb2xvciA9ICJicm93biIpCmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IFNwZWNpZXMpKQpnZ3Bsb3QoaXJpcykgKyBnZW9tX3BvaW50KGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSApICsgZ2VvbV9zbW9vdGgoYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKQpgYGAKCjxicj4KCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICIxMDAlIn0KcDEgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoKQpwMiA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkKcDMgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KGNvbG9yID0gInB1cnBsZSIpICsgZ2VvbV9zbW9vdGgoKQpwNCA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKGNvbG9yID0gImJyb3duIikKcDUgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gU3BlY2llcykpCnA2IDwtIGdncGxvdChpcmlzKSArIGdlb21fcG9pbnQoYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpICkgKyBnZW9tX3Ntb290aChhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llcykpCgpwMSArIHAyICsgcDMgKyBwNCArIHA1ICsgcDYgKyBwbG90X2xheW91dChuY29sID0gMikKYGBgCgoKCkVuIGxhIHRlcmNlcmEgZXhwcmVzacOzbiBzZSBlc3BlY2lmaWNhIGBjb2xvciA9IFNwZWNpZXNgIGRlbnRybyBkZSBgYWVzKClgIGVuIGBnZ3Bsb3QoKWAsIGFzw60gcXVlLCBkZSBtb21lbnRvLCB0b2RvcyBsb3MgZ2VvbXMgZGVsIGdyw6FmaWNvIGRlYmVyw61hbiBkaWZlcmVuY2lhciBwb3IgZXNwZWNpZXMgZGUgbGlyaW9zIHVzYW5kbyBlbCBjb2xvciwgUEVSTywgZGVzcHXDqXMgc2UgdnVlbHZlIGEgdXNhciBlbCBhcmd1bWVudG8gYGNvbG9yYCBkZW50cm8gZGUgYGdlb21fcG9pbnQoKWAsIHBlcm8gZsOtamF0ZSBxdWUgbm8gdmEgZGVudHJvIGRlIGRlIGBhZXMoKWAsIHZhIGZ1ZXJhLiBDb25jcmV0YW1lbnRlIGhhY2Vtb3MgbG8gc2lndWllbnRlOiBgZ2VvbV9wb2ludChjb2xvciA9ICJwdXJwbGUiKWA7IGVzIGRlY2lyLCBwYXJhIGxhIGNhcGEgZGUgcHVudG9zLCB5IHNvbG8gcGFyYSBsYSBjYXBhIGRlIHB1bnRvcyBxdWUgc2UgY3JlYSBjb24gYGdlb21fcG9pbnQoKWAsIGVzdGFtb3MgYXNvY2lhbmRvIGxhIGVzdMOpdGljYSBjb2xvciwgbm8gYSB1bmEgdmFyaWFibGUsIHNpbm8gYSB1biBjb2xvciBmaWpvLiBTaW4gZW1iYXJnbywgcGFyYSBsYSBvdHJhIGNhcGEgZGVsIGdyw6FmaWNvLCBsYSBxdWUgcmVzdWx0YSBkZSB1c2FyIGBnZW9tX3Ntb290aCgpYCBzaWd1ZSBzaWVuZG8gdmFsaWRvIHF1ZSBsYSBlc3TDqXRpY2EgY29sb3IgZXN0w6EgYXNvY2lhZGEgYSBsYSB2YXJpYWJsZSBTcGVjaWVzLiAKCgpVbiBkZXRhbGxlOiBpbWFnaW5hIHF1ZSBlbiB1biBncsOhZmljbyBlbiBlbCBxdWUgaGFzIGZpamFkbyAzIGVzdMOpdGljYXMgZGVudHJvIGRlIGBnZ3Bsb3QoYWVzKCkpYC4gRW4gcHJpbmNpcGlvIGxhcyAzIGVzdMOpdGljYXMgYWZlY3RhcsOhbiBhIHRvZG9zIGxvcyBgZ2VvbV94eCgpYCBxdWUgdXRpbGljZXMgZW4gdHUgZ3LDoWZpY28sIHBlcm8gc2kgcXVpc2llcmFzIHF1ZSwgcG9yIGVqZW1wbG8sIGxhIGVzdMOpdGljYSBjb2xvciBubyBhZmVjdGFzZSBhIHVuIGdlb20gY29uY3JldG8sIHBvZHLDrWFzIGhhY2VyIGxvIHNpZ3VpZW50ZTogYGdlb21feHgoYWVzKGNvbG9yID0gTlVMTCkpYC4gICAKCgpDb21vIHB1ZWRlcyBpbWFnaW5hciwgYcO6biB0ZW5lbW9zIHF1ZSB2ZXIgbcOhcyBlbGVtZW50b3MgZGUgYGdncGxvdDJgLiBDb21vIG3DrW5pbW8gbG9zIHTDrXR1bG9zIHkgbGV5ZW5kYXMsIGxvcyBlamVzLCBlbCB0ZW1hLCBjb29yZGVuYWRhcywgZXRjLi4uIHZhbW9zIGEgZWxsbyEhCgo8YnI+CgoKIyMgMy4gRWxlbWVudG9zIGRlIHVuIGdncGxvdAoKCllhIGhlbW9zIHByZXNlbnRhZG8gbG9zIHByaW5jaXBhbGVzIGVsZW1lbnRvcyBkZSBsb3MgZ3LDoWZpY29zIGhlY2hvcyBjb24gYGdncGxvdDJgLCBsb3MgcXVlIHRpZW5lbiBxdWUgdmVyIGNvbiBsYSByZXByZXNlbnRhY2nDs24gZGUgbGFzIHZhcmlhYmxlcy4gUGVybyBlcyBldmlkZW50ZSBxdWUgdW4gZ3LDoWZpY28gdGllbmUgbXVjaG9zIG3DoXMgZWxlbWVudG9zLCB5IGzDs2dpY2FtZW50ZSBoYXkgcXVlIGNvbm9jZXJsb3MgdW4gcG9jbyAgcGFyYSBwb2RlciBhanVzdGFyIGxvcyBncsOhZmljb3MgYSBudWVzdHJhcyBuZWNlc2lkYWRlcyB5IG1lam9yYXIgbGEgY2FsaWRhZCBkZSBudWVzdHJvcyBncsOhZmljb3MuIAoKRWplbXBsb3MgZGUgb3Ryb3MgZWxlbWVudG9zIHNvbjogdMOtdHVsb3MgZGVsIGdyw6FmaWNvIHkgZGUgbG9zIGVqZXMsICJ0aGVtZSIgZGVsIGdyw6FmaWNvLCBzbWFsbCBtdWx0aXBsZXMgbyBmYWNldGluZywgYW5vdGFjaW9uZXMgZXRjLi4uCgpFbiBlc3TDoSBzZWNjacOzbiBpcmVtb3MgbcOhcyByw6FwaWRvLiBTZSBwcmVzZW50YXLDoW4gc29sYW1lbnRlIGFsZ3Vub3MgZWplbXBsb3MsIGNvbmNlcHRvcyB5L28gYWNsYXJhY2lvbmVzLiBTaSBuZWNlc2l0YXMgcHJvZnVuZGl6YXIgbcOhcyBlbiBlc3RvcyBlbGVtZW50b3MsIHB1ZWRlcyBhY3VkaXIgYSBsYSBbcmVmZXJlbmNpYSBvZmljaWFsXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW5kZXguaHRtbCNzZWN0aW9uLWxheWVyLWdlb21zKSBkZSBgZ2dwbG90MmAgbyBhbCBbYm9ra2Rvd24gZGUgZ2dwbG90Ml0oaHR0cHM6Ly9nZ3Bsb3QyLWJvb2sub3JnLykuCgoKWWEgZGlqaW1vcyBxdWUgbG9zIGdyw6FmaWNvcyBnZ3Bsb3Qgc2UgY29tcG9uZW4gZGUgY2FwYXMgbyBsYXllcnMuIFBhcmEgbm9zb3Ryb3MsIGhhc3RhIGFob3JhLCB1bmEgY2FwYSBlc3RhYmEgY29tcHVlc3RhIGRlIDMgZWxlbWVudG9zOgoKICAtIHVuIGNvbmp1bnRvIGRlIGRhdG9zICAKICAKICAtIHVuIGNvbmp1bnRvIGRlIHZhcmlhYmxlcyBtYXBlYWRhcyBjb24gYGFlcygpYCBhIHByb3BpZWRhZGVzIGVzdMOpdGljYXMgIAogIAogIC0gdW5hIGdlb21ldHLDrWEsIGNvbiBgZ2VvbV94eCgpYCAKICAKRXMgZXZpZGVudGUgcXVlIGVuIHRvZG9zIGxvcyBnZW9tcyBubyBzZSBwdWVkZW4gZXNwZWNpZmljYXIgdG9kYXMgbGFzIGNhcmFjdGVyw61zdGljYXMgZXN0w6l0aWNhcy4gUG9yIGVqZW1wbG8gc2kgdXNhcyBgZ2VvbV9wb2ludGAgbm8gcG9kcsOhcyBlc3BlY2lmaWNhciBsYSBhbmNodXJhIG8gZWwgdGlwbyBkZSBsYXMgbGluZWFzLCBwb3JxdWUgbm8gZXN0w6FzIHVzYW5kbyBsaW5lYXMgc2lubyBwdW50b3MuIFBhcmEgdmVyIHF1ZSBlc3TDqXRpY2FzIGFkbWl0ZSBjYWRhIGdlb20gdGVuZHLDoXMgcXVlIG1pcmFyIGxhIGF5dWRhIGRlIGNhZGEgZ2VvbS4gQWwgZmluYWwgZGUgW2VzdGUgcG9zdF0oaHR0cHM6Ly93d3cueWloYW53dS5jYS9wb3N0L2dlb21zLWFuZC1hZXN0aGV0aWMtcGFyYW1ldGVycy8pIHRpZW5lbiB1biBncsOhZmljbyBpbnRlcmFjdGl2byBjb24gZWwgcXVlIHNlIHB1ZWRlICB2ZXIgZsOhY2lsbWVudGUgcXVlIGNhcmFjdGVyw61zdGljYXMgZXN0w6l0aWNhcyBhZG1pdGUgY2FkYSBnZW9tLiBQb3IgZWplbXBsbyBgZ2VvbV9iYXIoKWAsIHF1ZSBzaXJ2ZSBwYXJhIGhhY2VyIGdyw6FmaWNvcyBkZSBiYXJyYXMsIG5vIGFkbWl0ZSBtYXBwZWFyIHZhcmlhYmxlcyBhbCBlamUgWSwgeWEgcXVlIGVuIGVsIGVqZSBZIHNlIHZpc3VhbGl6YW4vbWFwZWFuIGxhcyBmcmVjdWVuY2lhcyBhYnNvbHV0YXMgbyByZWxhdGl2YXMgZGUgbGEgdmFyaWFibGUgcXVlIHNlIHJlcHJlc2VudGEgZW4gZWwgZWplIFguCgo8YnI+CgpFc3RvIGVzIGxvIGLDoXNpY28gcXVlIGhheSBxdWUgc2FiZXIsIHBlcm8gZW4gcmVhbGlkYWQsIHVuYSBjYXBhIG5lY2VzaXRhIGRlIGRvcyBlbGVtZW50b3MgbcOhczogdW5hICoqc3RhdCoqIChvIHRyYW5zZm9ybWFjacOzbiBlc3RhZMOtc3RpY2EpIHkgdW5hICoqcG9zaWNpw7NuKiouICBFc3RvcyBkb3Mgw7psdGltb3MgZWxlbWVudG9zIHNvbiBuZWNlc2FyaW9zIHBlcm8gbGEgdmVyZGFkIGVzIHF1ZSBwb2Ryw61hbW9zIHNlZ3VpciBoYWNpZW5kbyBncsOhZmljb3MgY29uIGBnZ3Bsb3QyYCBzaW4gY29ub2Nlcmxvcy4gwr9Qb3IgcXXDqT8gUHVlcyBwb3JxdWUgc2kgZW4gdW5hIGNhcGEgbm8gbG9zIGVzcGVjaWZpY2Ftb3MsIGxvIGhhY2UgYGdncGxvdDJgIHBvciBub3NvdHJvcy4gTG8gaGEgZXN0YWRvIGhhY2llbmRvIGhhc3RhIGFob3JhIGVuIHRvZG9zIGxvcyBncsOhZmljb3MgcXVlIGxsZXZhbW9zIGhlY2hvcy4gUGVybyBjbGFybywgc2FiZXIgY29tbyB1dGlsaXphciBlc3RvcyBlbGVtZW50b3Mgbm9zIGRhcsOhIG3DoXMgZmxleGliaWxpZGFkIGEgbGEgaG9yYSBkZSBoYWNlciBnZ3Bsb3RzLgoKCkdlbmVyYWxtZW50ZSBsYXMgY2FwYXMgc2UgdmFuIGHDsWFkaWVuZG8gY29uIGxhIGZhbWlsaWEgZGUgZnVuY2lvbmVzIGBnZW9tX3h4KClgLCAqKlBFUk8qKiB0YW1iacOpbiBzZSBwdWVkZW4gYcOxYWRpciBjYXBhcyBjb24gb3RyYSBmYW1pbGlhIGRlIGZ1bmNpb25lcyBgc3RhdF94eCgpYC4gCgoKQXBhcnRlIGRlIGVzdG9zIGNpbmNvIGVsZW1lbnRvcyAoZGF0b3MsIGFlcygpLCBnZW9tLCBzdGF0IHkgcG9zaWNpw7NuKSBsb3MgZ3LDoWZpY29zIGdncGxvdCBwdWVkZW4gdGVuZXIgbcOhcyBlbGVtZW50b3MuIFZlw6Ftb3Nsb3MgdW5vIGEgdW5vLgoKPGJyPgoKIyMjIDMuMSBUw610dWxvcyBkZWwgZ3LDoWZpY28KCkVzIGV2aWRlbnRlIHF1ZSB1biBncsOhZmljbyBwYXJhIHNlciBlZmVjdGl2byB5IG1vc3RyYXIgc3UgbWVuc2FqZSBjb24gY2xhcmlkYWQgZGViZSB0ZW5lciB1biB0w610dWxvIHkvbyBzdWJ0w610dWxvIGlsdXN0cmF0aXZvIHkgZGViZSBtb3N0cmFyIGluZm9ybWFjacOzbiByZWxldmFudGUgc29icmUgcXVlIHZhcmlhYmxlcyBzZSBncmFmaWNhbiBsb3MgZWplcyBYIGUgWS4gRXN0ZSB0aXBvIGRlIGVsZW1lbnRvcyBwdWVkZW4gbW9kaWZpY2Fyc2UgZGUgdmFyaWFzIG1hbmVyYXMsIHBlcm8gbm9zIGNlbnRyYXJlbW9zIGVuIGxhIGZ1bmNpw7NuIGBsYWJzKClgLiAKCkbDrWphdGUgcXVlIGNvbiBsYSBmdW5jacOzbiBgbGFicygpYCwgZGUgbGFiZWxzLCBwb2RlbW9zIGNhbWJpYXIgbG9zIHTDrXR1bG9zIGRlbCBncsOhZmljbywgZGUgbG9zIGVqZXMgeSB0YW1iacOpbiBkZSBsYXMgbGV5ZW5kYXMuIAoKRW4gbG9zIHTDrXR1bG9zICh0YW50byBkZWwgZ3LDoWZpY28sIGNvbW8gZGUgbG9zIGVqZXMgeSBsZXllbmRhcykgdGFtYmnDqW4gc2UgcHVlZGVuIGNhbWJpYXIgb3RyYXMgY2FyYWN0ZXLDrXN0aWNhczsgcG9yIGVqZW1wbG8sIGNhbWJpYXIgZWwgdGFtYcOxbywgbGEgZnVlbnRlIG8gZWwgY29sb3IsIHBlcm8gZXNvIHNlcsOhIHRhcmVhIGRlIG90cmEgZnVuY2nDs24gZGUgYGdncGxvdDJgOiBkZWwgZ3J1cG8gZGUgZnVuY2lvbmVzIGB0aGVtZV8oKWAuIFBlcm8gZWwgdGVtYSBvIHRoZW1lIGRlIGxvcyBncsOhZmljb3MgbG8gdmVyZW1vcyBlbiBlbCBzaWd1aWVudGUgYXBhcnRhZG8uCgoKVG9tZW1vcyBlbCBzaWd1aWVudGUgZ3LDoWZpY28gY29tbyByZWZlcmVuY2lhIHkgc29icmUgw6lsIGlyZW1vcyBhw7FhZGllbmRvIGVsZW1lbnRvczoKCmBgYHtyfQpwIDwtIGdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llcykpICsgZ2VvbV9wb2ludCgpCnAKYGBgCgpDb24gbGEgZnVuY2nDs24gYGxhYnMoKWAgcG9kZW1vcyBhw7FhZGlybGUgdW4gdMOtdHVsbywgc3VidGl0dWxvLCBwaWUgZGUgZ3LDoWZpY28gbyAqY2FwdGlvbiouIFRhbWJpw6luIHBvZGVtb3MgY2FtYmlhciBlbCB0w610dWxvIGRlIGxvcyBlamVzIFggZSBZLCBhc8OtIGNvbW8gdGFtYmnDqW4gZWwgdGl0dWxvIGRlIGxhIGxleWVuZGEgcGFyYSBgY29sb3JgLCBvIHBhcmEgb3RyYXMgZXN0w6l0aWNhcyBxdWUgdXRpbGljZW1vcyBlbiBlbCBncsOhZmljby4KCkVzIHN1ZmljaWVudGUgY29uIHZlciB1biBlamVtcGxvOgoKCmBgYHtyfQpwICsgbGFicyh0aXRsZSA9ICJHcsOhZmljbyAxOiBMb25naXR1ZCBkZWwgc8OpcGFsbyBmcmVudGUgYWwgcMOpdGFsbyIsCiAgICAgICBzdWJ0aXRsZSA9ICIoZGlmZXJlbmNpYW5kbyBwb3IgZXNwZWNpZSBkZSBsaXJpbykiLAogICAgICAgY2FwdGlvbiA9ICJEYXRvcyBwcm92ZW5pZW50ZXMgZGVsIElyaXMgZGF0YXNldCIsCiAgICAgICB4ID0gIkxvbmdpdHVkIGRlbCBzw6lwYWxvIiwKICAgICAgIHkgPSAiTG9uZ2l0dWQgZGVsIHDDqXRhbG8iLAogICAgICAgY29sb3IgPSAiRXNwZWNpZSBkZSBsaXJpbyIpCmBgYAoKClNpIHF1aXNpZXJhcyBlbGltaW5hciBjb21wbGV0YW1lbnRlIGxvcyAqKnTDrXR1bG9zKiogIGRlbCBlamUgWCBwb2Ryw61hcyBoYWNlcmxvIGVuIGVsIGFudGVyaW9yIGNodW5rIGZpamFuZG8gYHggPSBOVUxMYCBkZW50cm8gZGUgbGEgZnVuY2nDs24gYGxhYnMoKWAuCgoKRW4gbHVnYXIgZGUgdXNhciBsYSBmdW5jacOzbiBgbGFicygpYCwgdGFtYmnDqW4gcG9kZW1vcyB1dGlsaXphciBsYXMgZnVuY2lvbmVzIGF1eGlsaWFyZXMgYHhsYWIoKWAgZSBgeWxhYigpYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgKyBsYWJzKGNvbG9yID0gTlVMTCwgeCA9IE5VTEwpICAjLSBib3JyYSBlbCB0w610dWxvIGRlIGxhIGxleWVuZGEgeSBkZWwgZWplIFgKcCArIHhsYWIoTlVMTCkgKyB5bGFiKE5VTEwpICAgICAgICMtIGVsaW1pbmEgdMOtdHVsb3MgZGUgbG9zIGVqZXMgWCBlIFkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjkwJSIsICBmaWcuYXNwID0gMy85fQpwMSA8LSBwICsgbGFicyhjb2xvciA9IE5VTEwsIHggPSBOVUxMKSAgIy0gYm9ycmEgZWwgdMOtdHVsbyBkZSBsYSBsZXllbmRhIHkgZGVsIGVqZSBYCnAyIDwtIHAgKyB4bGFiKE5VTEwpICsgeWxhYihOVUxMKSAgICAgICAjLSBlbGltaW5hIHTDrXR1bG9zIGRlIGxvcyBlamVzIFggZSBZCgpwMSArIHAyICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKCgoKIyMjIDMuMiBUaGVtZXMKCj4gVGhlbWVzIGNvbnRyb2wgdGhlIGRpc3BsYXkgb2YgYWxsIG5vbi1kYXRhIGVsZW1lbnRzIG9mIHRoZSBwbG90LiBZb3UgY2FuIG92ZXJyaWRlIGFsbCBzZXR0aW5ncyB3aXRoIGEgY29tcGxldGUgdGhlbWUgbGlrZSB0aGVtZV9idygpLCBvciBjaG9vc2UgdG8gdHdlYWsgaW5kaXZpZHVhbCBzZXR0aW5ncyBieSB1c2luZyB0aGVtZSgpIGFuZCB0aGUgZWxlbWVudF8gZnVuY3Rpb25zLiBVc2UgdGhlbWVfc2V0KCkgdG8gbW9kaWZ5IHRoZSBhY3RpdmUgdGhlbWUsIGFmZmVjdGluZyBhbGwgZnV0dXJlIHBsb3RzLgoKUGFyYSBjYW1iaWFyIGRldGFsbGVzIGRlIGxhIGFwYXJpZW5jaWEgZGVsIGdyw6FmaWNvIGNvbW8gZWwgdGFtYcOxbywgZnVlbnRlcyB5IGNvbG9yIGRlIGxvcyB0w610dWxvcywgcGVybyB0YW1iacOpbiBkZSBsb3MgcHVudG9zLCBsYXMgbGluZWFzLCBlbCBmb25kbyBkZWwgZ3LDoWZpY28sIGxhIGFwYXJpZW5jaWEgZGUgbGFzIGdyaWQtbGluZXMsIGVsIGx1Z2FyIHBhcmEgbGFzIGxleWVuZGFzLCBldGMuLi4gZXRjLi4uIGNvbnRhbW9zIGNvbiBsYXMgImZ1bmNpb25lcyBkZSB0ZW1hIjsgdG9kYXMgZWxsYXMgY29taWVuemFuIGNvbiBgdGhlbWVfKClgCgpFbiBnZW5lcmFsIGNvbiBsYXMgZnVuY2lvbmVzIGB0aGVtZV8oKWAgcG9kZW1vcyBjYW1iaWFyL2FqdXN0YXIgY3VhbHF1aWVyIGVsZW1lbnRvIGRlbCBncsOhZmljbywgY29uIGxhIGV4Y2VwY2nDs24gZGUgbGEgcHJvcGlhIHJlcHJlc2VudGFjacOzbiBkZSBsb3MgZGF0b3MgKHlhIHNhYmVtb3MgcXVlIGVzdG8gc2UgaGFjZW4gY29uIGxhcyBmdW5jaW9uZXMgYGdlb21fKClgKS4gRXN0b3MgZWxlbWVudG9zIGFmZWN0YW4gYSBsYSBhcGFyaWVuY2lhIHkgZGV0YWxsZXMgZGVsIGdyw6FmaWNvLCBwZXJvIG5vIGEgbGEgcmVsYWNpw7NuIGVudHJlIHZhcmlhYmxlcyBxdWUgc2UgbXVlc3RyYSByZWFsbWVudGUgZW4gZWwgZ3LDoWZpY28uCgoKUGFyYSBlbXBlemFyIGEgZW50ZW5kZXIgcXVlIGhhY2VuIGxhcyBmdW5jaW9uZXMgcmVsYWNpb25hZGFzIGNvbiBlbCB0aGVtZSwgc2XDsWFsYXIgcXVlIGBnZ3Bsb3QyYCBpbmNvcnBvcmEgdW4gY29uanVudG8gZGUgInRlbWFzIiBxdWUgcG9kZW1vcyB1dGlsaXphciBwYXJhIGNhbWJpYXIgbGEgYXBhcmllbmNpYSBkZWwgZ3LDoWZpY28gYSBudWVzdHJvIGd1c3RvLiBQdWVkZXMgdmVybG9zIHRvZG9zIFthcXXDrV0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2dndGhlbWUuaHRtbCkuIEVsIHRlbWEgcXVlIHVzYSBwb3IgZGVmZWN0byBgZ2dwbG90MmAgZXMgYHRoZW1lX2dyYXkoKWAuIFZlYW1vcyBhIGxvcyB0aGVtZXMgZW4gYWNjacOzbjoKCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCArIHRoZW1lX2dyYXkoKSAgICMtIHRlbWEgcG9yIGRlZmVjdG8KcCArIHRoZW1lX2xpZ2h0KCkKcCArIHRoZW1lX2RhcmsoKQpwICsgdGhlbWVfY2xhc3NpYygpCnAgKyB0aGVtZV9taW5pbWFsKCkKcCArIHRoZW1lX3ZvaWQoKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiOTAlIiwgIGZpZy5hc3AgPSA1Lzl9CnAxIDwtIHAgKyB0aGVtZV9ncmF5KCkgICAjLSB0ZW1hIHBvciBkZWZlY3RvCnAyIDwtIHAgKyB0aGVtZV9saWdodCgpCnAzIDwtIHAgKyB0aGVtZV9kYXJrKCkKcDQgPC0gcCArIHRoZW1lX2NsYXNzaWMoKQpwNSA8LSBwICsgdGhlbWVfbWluaW1hbCgpCnA2IDwtIHAgKyB0aGVtZV92b2lkKCkKCnAxICsgcDIgKyBwMyArIHA0ICsgcDUgKyBwNiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKCgoKRWwgcGFxdWV0ZSBkZSBSIFtnZ3RoZW1lc10oaHR0cHM6Ly9qcm5vbGQuZ2l0aHViLmlvL2dndGhlbWVzL3JlZmVyZW5jZS9pbmRleC5odG1sKSBpbmNvcnBvcmEgdW5hIGFtcGxpYSBsaXN0YSBkZSB0ZW1hcyBhZGljaW9uYWxlcywgYWxndW5vcyBkZSBlbGxvcyB0cmF0YW4gZGUgcmVwbGljYXIgZWwgZXN0aWxvIGRlIGNvcnBvcmFjaW9uZXMgZmFtb3NhcyBjb21vIFRoZSBFY29ub21pc3QgbyBTdGF0YS4gRW4gZWwgdHV0b3JpYWwgbm8gc2UgdmVuIGJpZW4gbG9zIGdyw6FmaWNvcyBwb3JxdWUgbG9zIGhlIGhlY2hvIHBlcXVlw7FpdG9zLCBwZXJvIHBydWViYSBhIGhhY2VybG9zIHTDuiBtaXNtbyB5IHZlcsOhcyBxdWUgc29uIGdvb2QtbG9va2luZy4gVmVhbW9zIGFsZ3Vub3M6CgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmxpYnJhcnkoZ2d0aGVtZXMpCnAgKyB0aGVtZV9lY29ub21pc3QoKSAgIApwICsgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkKcCArIHRoZW1lX3N0YXRhKCkKcCArIHRoZW1lX3NvbGFyaXplZCgpCmBgYAoKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiODUlIiwgIGZpZy5hc3AgPSA1Lzl9CmxpYnJhcnkoZ2d0aGVtZXMpCnAxIDwtIHAgKyB0aGVtZV9lY29ub21pc3QoKSAKcDIgPC0gcCArIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpCnAzIDwtIHAgKyB0aGVtZV9zdGF0YSgpICAKcDQgPC0gcCArIHRoZW1lX3NvbGFyaXplZCgpCgpwMSArICBwMiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpwMyArICBwNCArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQoKCmBgYAoKCgoKVGFtYmnDqW4gcG9kZW1vcyBkZWZpbmlyIHVuIHRlbWEgcHJvcGlvIHBhcmEgcXVlIGVsIGdyw6FmaWNvIHNlIGFqdXN0ZSBsb3MgbcOhcyBwb3NpYmxlIGEgbnVlc3RyYXMgcHJlZmVyZW5jaWFzLgoKCmBgYHtyLCBvdXQud2lkdGggPSAiNTAlIiwgIGZpZy5hc3AgPSA0Lzl9CiMgZGVmaW5lIGN1c3RvbSB0aGVtZQpteV90aGVtZSA8LSB0aGVtZShheGlzLnRleHQueCA9IAogICAgICAgICAgICAgICAgICBlbGVtZW50X3RleHQoY29sb3VyID0gImdyZXkyMCIsIHNpemUgPSAxMiwgYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41KSAsICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJncmV5MjAiLCBzaXplID0gMTIpICwKICAgICAgICAgICAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpKQpwICsgbXlfdGhlbWUKYGBgCgoKU2UgcHVlZGUgZmlqYXIgZWwgdGVtYS90aGVtZSBkZSBsb3MgZ3LDoWZpY29zIGNvbiBsYSBmdW5jacOzbiBgdGhlbWVfc2V0KClgLiBQb3IgZWplbXBsbzoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpICAjLSB1biB0ZW1hIGNvbmNyZXRvCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkgKyAKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSkpICMtIHVuIHRlbWEgbW9kaWZpY2FuZG8gYWxndW5hcyBvcGNpb25lcywgcXVlIGVsZSBlamUgeCBubyBtdWVzdHJlIHRpY2tzIG5pIGVzY2FsYXMKYGBgCgpTaSBxdWllcmVzIHZvbHZlciBhbCB0aGVtZSBwb3IgZGVmZWN0bzoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQp0aGVtZV9zZXQodGhlbWVfZ3JheSgpKQpgYGAKCgpFamVtcGxvcyBkZSBhbGd1bm9zIGVsZW1lbnRvcyBjdXlhIGFwYXJpZW5jaWEgcXVlIHNlIHB1ZWRlbiBjYW1iaWFyIGNvbiBgdGhlbWUoKWAKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAgICAgICAgICAgIy0gcXVlIG5vIGFwYXJlemNhIGxleWVuZGEKcCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSAgICAgICAgICAjLSBsZXllbmRhIGFiYWpvCnAgKyB0aGVtZShsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKSAgICAgIy0gbGV5ZW5kYSBob3Jpem9udGFsISEKcCArIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMjIpKSAgICAgIy0gdMOtdHVsbyBkZSBsYSBsZXllbmRhIGEgMjIKcCArIHRoZW1lKGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMi40LCAiY20iKSkgICAgICAgICAgIy0gdGFtYcOxbyBkZSBsb3MgY3VhZHJvcyBkZSBsYSBsZXllbmRhCgoKcCArIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwLCBmYWNlID0gImJvbGQiKSkgICAgICAgICAjLSBjYW1iaWFyIGVsIHRhbWHDsW8gZGUgdG9kb3MgbG9zIGVsZW1lbnRvcyBkZSB0ZXh0bwpwICsgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkgICAgICAgICAgICAgICAgICAgICMtIHBvbmUgZW4gbmVncml0YSB0b2RvcyBsb3MgZWxlbWVudG9zIGRlIHRleHRvCgpwICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3VyID0gInBpbmsiLCBzaXplID0gMTIsIGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSkpICMgYXBhcmllbmNpYSBkZSBsYSBlc2NhbGEgZGVsIGVqZSB4CgpwICsgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MjUsIGFuZ2xlID0gNDUpKSAjLSB0YW1hw7FvIHkgYW5ndWxvIGRlbCB0ZXh0byBkZWwgZWplIFkKCnAgKyB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMykpICAgIy0gcG9zaWNpw7NuIGhvcml6b250YWwgZGVsIHN1YnRpdHVsbyAoc2kgbG8gdHV2aWVzZSkKICAKcCArIHRoZW1lKHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDMpKSAgICAjLSBwb3NpY2nDs24gdmVydGljYWwgZGVsIHBpZSBkZSBncsOhZmljbyAoc2kgbG8gdHV2aWVzZSkKCnAgKyB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JlZW4iLCBjb2xvdXIgPSAicGluayIsIGxpbmV0eXBlID0gImxvbmdkYXNoIiwgc2l6ZSA9IDMuNSkpCnAgKyB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpwICsgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IE5VTEwpCgoKcCArIHRoZW1lKHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInBpbmsiLCBjb2xvdXIgPSAicHVycGxlIiwgbGluZXR5cGUgPSAiZG90dGVkIiwgc2l6ZSA9IDcpKQpgYGAKCgpTaSBxdWllcmVzIHZlciB0b2RvcyBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBxdWUgY29udHJvbGEgeSBxdWUgcG9yIHRhbnRvIHB1ZWRlcyBtb2RpZmljYXIgY29uIGB0aGVtZSgpYCwgdXNhIGxhIGF5dWRhIGRlIGxhIGZ1bmNpw7NuIGB0aGVtZSgpYCBvIGVqZWN1dGEgZW4gUiBgYXJncyh0aGVtZSlgLiBBdW5xdWUgY2FzaSBtZWpvciB2ZXJsYXMgZW4gW2VzdGEgaW5mb2dyYWbDrWFdKGh0dHBzOi8vaGVucnl3YW5nLm5sL2dncGxvdDItdGhlbWUtZWxlbWVudHMtZGVtb25zdHJhdGlvbi8pIGRlIEhlbnJ5IFdhbmcgbyBlbiBbZXN0ZSBwb3N0XShodHRwczovL2lzYWJlbGxhLWIuY29tL2Jsb2cvZ2dwbG90Mi10aGVtZS1lbGVtZW50cy1yZWZlcmVuY2UvKSBkZSBJc2FiZWxsYSBCZW5hYmF5ZSBjb24gbGFzIG9wY2lvbmVzIHF1ZSBzdWVsZSBjYW1iaWFyIGVsbGEuCgoKCkVuIGdlbmVyYWwsIHNpIHF1aWVyZXMgY2FtYmlhciBhbGfDum4gZWxlbWVudG8gZGUgdW4gZ2dwbG90LCBoYXMgZGUgaGFjZXIgYHRoZW1lKGVsZW1lbnRvID0gZWxlbWVudF90ZXh0KCkpYC4gU2kgcXVpZXJlcyBlbGltaW5hciBwb3IgY29tcGxldG8gYWxnw7puIGVsZW1lbnRvIGRlbCBncsOhZmljbywgcG9yIGVqZW1wbG8gbGFzIGdyaWQtbGluZXMgZGVsIGdyw6FmaWNvLCBoYXLDrWFzIGB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKWAuCgoKRXZpZGVudGVtZW50ZSB0b2RvIGVzdG8gZXMgaW1wb3NpYmxlIGRlIGFwcmVuZGVyLCBzw7NsbyB0aWVuZXMgcXVlIHNhYmVyIHF1ZSBjdWFscXVpZXIgZWxlbWVudG8gZGVsIGdyw6FmaWNvIHNlIHB1ZWRlIGNhbWJpYXIgeSB0aWVuZXMgcXVlIHNhYmVyIGJ1c2NhciBlIGludGVycHJldGFyIGxhIGF5dWRhLgoKIyMjIyBDb2xvcmVzCgoKU2UgcG9jbyBkZSBjb2xvcmVzLCBwZXJvIGNsYXJvIHNpIHF1aWVyZXMgY2FtYmlhciBsYSBhcGFyaWVuY2lhIGRlIGxvcyBncsOhZmljb3MsIGhhcyBkZSBzYWJlciBsb3MgY29sb3JlcyBkZSBSLCBhc8OtIHF1ZToKCi0gW0FxdcOtXShodHRwOi8vc2FwZS5pbmYudXNpLmNoL3F1aWNrLXJlZmVyZW5jZS9nZ3Bsb3QyL2NvbG91cikgdGllbmVzIHVuYSBndcOtYSBwYXJhIGVsZWdpciBjb2xvci4gU2kgc29sbyBxdWllcmVzIHZlciBsYSBsaXN0YSBkZSBub21icmVzIGRlIGxvcyBjb2xvcmVzIGVuIFIgZWplY3V0YTogYGFhIDwtIGFzLmRhdGEuZnJhbWUoY29sb3VycygpKWAKCi0gU2kgc2FiZXMgZWwgbm9tYnJlIGRlbCBjb2xvciBxdWUgcXVpZXJlcywgW2FxdcOtXShodHRwczovL3BrZy5nYXJyaWNrYWRlbmJ1aWUuY29tL3ItY29sb3JzLWNzcy8pLCAgcG9kcsOhcyBidXNjYXJsbyB5IHZlciBzdSBjb2xvciB5IHN1IGNvZGlmaWNhY2nDs24gZW4gUkdCIHkgSGV4LgoKCi0gVGFtYmnDqW4gZXMgaW50ZXJlc2FudGUgZWwgcGFxdWV0ZSBbcGFsZXRlZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9FbWlsSHZpdGZlbGR0L3BhbGV0dGVlcikgcXVlIGFncnVwYSB1biBjb25qdW50byBhbXBsaW8gZGUgcGFsZXRhcyBkZSBjb2xvcmVzIHBhcmEgdXNhciBlbiBSLiAKCjxicj4KCiMjIyMgWEtDRCB0aGVtZQoKUG9yIMO6bHRpbW8sIG5vIHPDqSBzaSBjb25vY8OpaXMgZWwgW3dlYmNvbWljIFhLQ0RdKGh0dHBzOi8veGtjZC5jb20vKS4gUHVlcyBlbiBSIHRhbWJpw6luIGhheSB1biBwYXF1ZXRlIHkgdW4gdGhlbWUgcGFyYSBoYWNlciBncsOhZmljb3MgYWwgZXN0aWxvIFhLQ0QuIEVzIGVsIFtwYXF1ZXRlIHhrY2RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy94a2NkL2luZGV4Lmh0bWwpIGN1eW8gYXV0b3IgZXMgRW1pbGlvIFRvcnJlcy1NYW56YW5lcmEgZGUgbGEgVW5pdmVyc2lkYWQgZGUgT3ZpZWRvLiBbQXF1w61dKGh0dHA6Ly94a2NkLnItZm9yZ2Uuci1wcm9qZWN0Lm9yZy8pIHBvZMOpaXMgdmVyIGFsZ3Vub3MgZ3LDoWZpY29zIGhlY2hvcyBjb24gZXN0ZSBlc3RpbG8gZW4gUi4gCgpJbnRlbnTDqSBoYWNlciB1biBncsOhZmljbyBjb24gc3UgdGhlbWUsIHBlcm8gZGVzYWZvcnR1bmFkYW1lbnRlIG5vIG1lIHNhbGnDszsgcGVybyBqdXN0byBhbCBkw61hIHNpZ3VpZW50ZSB2aSBbZXN0ZSB0d2VldF0oaHR0cHM6Ly90d2l0dGVyLmNvbS9fR2lsX0hlbnJpcXVlcy9zdGF0dXMvMTE2NjQ0MDI2Mjc3MzU2NzQ4OD9zPTA5KSBxdWUgaGFjZSBhbGdvIHBhcmVjaWRvIGNvbiBkYXRvcyBkZSBsb3MgU2ltcHNvbnMgeSBbc3UgY8OzZGlnb10oaHR0cHM6Ly9naXRodWIuY29tL0dpbEhlbnJpcXVlcy9UaWR5VHVlc2RheXMvYmxvYi9tYXN0ZXIvMjAxOS0wOC0yNyUyMFNpbXBzb25zJTIwZ3Vlc3QlMjBzdGFycy9zaW1wc29ucy5SKSBzw60gbWUgaGEgZnVuY2lvbmFkbywgYWRlbcOhcyBzaW11bGEvY29uc3RydXllIGVsIGVzdGlsbyBYS0NEIGRlc2RlIGNlcm8uIAoKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBmaWcuYXNwID0gNy85fQp0d2VldHJtZDo6dHdlZXRfZW1iZWQoImh0dHBzOi8vdHdpdHRlci5jb20vX0dpbF9IZW5yaXF1ZXMvc3RhdHVzLzExNjYzNzM4NDQwNDAzMzUzNjAiLCB0aGVtZSA9ICJsaWdodCIsIGFsaWduID0gImNlbnRlciIsIG1heHdpZHRoID0gNDAwKQpgYGAKCgoKQWRlbcOhcywgZGVzcHXDqXMgdmkgcXVlIEV2YW5nZWx5bmUgUmVpbm9sZHMgaGl6byBbZXN0YSBtYXJhdmlsbGFdKGh0dHBzOi8vZXZhbWFlcmV5LmdpdGh1Yi5pby90aWR5dHVlc2RheV93YWxrX3Rocm91Z2gvc2ltcHNvbnMuaHRtbCMxKS4gTG8gaGFyZW1vcyBlbiBjbGFzZT8hIEVuIFtlc3RlIHBvc3RdKGh0dHBzOi8vYmxvZy5yZXZvbHV0aW9uYW5hbHl0aWNzLmNvbS8yMDE4LzA5L2N1cnZlLWZpdHRpbmcuaHRtbCkgcHVlZGVzIGVuY29udHJhciBlbCBjw7NkaWdvIHBhcmEgcmVwcm9kdWNpciB1bmEgZGUgbGFzIGhpc3RvcmlhcyBvIHZpw7FldGFzIGRlIFhLQ0QuCiAKCjxicj4KCgojIyMgMy4zIFNtYWxsIG11bHRpcGxlcyBvIEZhY2V0dGluZwoKCkVsIHNpc3RlbWEgZ3LDoWZpY28gZGUgYGdncGxvdDJgIGluY29ycG9yYSB1bmEgdMOpY25pY2EgZXNwZWNpYWwgbGxhbWFkYSAiZmFjZXRpbmciIHF1ZSBwZXJtaXRlIGRpdmlkaXIgdW4gZ3LDoWZpY28gZW4gbcO6bHRpcGxlcyBncsOhZmljb3MuIENhZGEgdW5vIGRlIGVzb3MgbcO6bHRpcGxlcyBncsOhZmljb3Mgc2UgcmVhbGl6YSBzw7NsbyBwYXJhIGxhcyBvYnNlcnZhY2lvbmVzIGRlIHVuYSBkZSBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgKG8gZmFjdG9yKSBpbmNsdWlkbyBlbiBlbCBjb25qdW50byBkZSBkYXRvcy4gRXMgbcOhcyBmw6FjaWwgaGFjZXJsbyBxdWUgZXhwbGljYXJsby9lc2NyaWJpcmxvLgoKUG9yIGVqZW1wbG8sIGVuIGBpcmlzYCB0ZW5lbW9zIGxhIHZhcmlhYmxlIGBTcGVjaWVzYCwgcXVlIGVzIGNhdGVnw7NyaWNhLiBMbyBxdWUgc2UgaGFjZSBjb24gZWwgImZhY2V0dGluZyIgZXMgZGl2aWRpciBlbCBkYXRhc2V0IGVuIGdydXBvcyB5IGhhY2VyIGVsIG1pc21vIGdyw6FmaWNvIHBhcmEgY2FkYSB1bm8gZGUgbG9zIGdydXBvcy4gTG9zIGdydXBvcyBzZSB2YW4gYSBkZWZpbmlyIGVuIGZ1bmNpw7NuIGRlIGxvcyB2YWxvcmVzIGRlIGxhIHZhcmlhYmxlIFNwZWNpZXMuIFJlY3VlcmRhIHF1ZSBoYXkgdHJlcyB0aXBvcyBvIGVzcGVjaWVzIGRlIGxpcmlvcy4KClBhcmEgaGFjZXIgdW4gImZhY2V0dGluZyBncmFwaCIgcG9kZW1vcyB1c2FyIGxhcyBmdW5jaW9uZXMgYGZhY2V0X3dyYXAoKWAgeSBgZmFjZXRfZ3JpZCgpYC4gCgpQb3IgZWplbXBsbywgY29uIGxhIGZ1bmNpw7NuIGBmYWNldF9ncmlkKClgIHB1ZWRlcyBlbGVnaXIgZW50cmUgaGFjZXIgbG9zIHNtYWxsIG11bHRpcGxlcyBwb3IgZmlsYXMgbyBwb3IgY29sdW1uYXMuIEVtcGVjZW1vcyBoYWNpZW5kbyB1biBmYWNldHRpbmcgKipwb3IgY29sdW1uYXMqKi4gQWRlbcOhcywgY29uIGBmYWNldF9ncmlkKClgIHNlIHB1ZWRlbiB1c2FyIHZhcmlhcyBzaW50YXhpcywgcGVybyBsYSBxdWUgYXBhcmVjZSBlbiBsYSBbY2hlYXRzaGVldCBhY3R1YWwgZGUgZ2dwbG90Ml0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLykgeSwgcG9yIHRhbnRvLCBsYSByZWNvbWVuZGFkYSBlcyBsYXMgcXVlIHZlcyBlbiBsYSBzZWd1bmRhIGxpbmVhOiAKCgpgYGB7ciwgb3V0LndpZHRoID0gIjEwMCUiLCBmaWcuYXNwID0gNC85fQojcCArIGZhY2V0X2dyaWQoIC4gfiBTcGVjaWVzKSAgICAgICAgICAgICAgICAjIG9sZCBzaW50YXhpcwpwICsgZmFjZXRfZ3JpZChjb2xzID0gdmFycyhTcGVjaWVzKSkgICAgICAgICAjIGdyw6FmaWNvcyB4IGNvbHVtbmFzLCBzZXBhcmFuZG8gcG9yIHZhbG9yZXMgZGUgJ1NwZWNpZXMnCmBgYAoKCkFob3JhIHBvciBmaWxhczoKCgpgYGB7ciwgb3V0LndpZHRoID0gIjUwJSIsIGZpZy5hc3AgPSA5Lzl9CnAgKyBmYWNldF9ncmlkKHJvd3MgPSB2YXJzKFNwZWNpZXMpKSAgICAgICAgICMgZ3LDoWZpY29zIHggZmlsYXMKYGBgCgoKVGFtYmnDqW4gcG9kZW1vcyB1dGlsaXphciBsYSBmdW5jacOzbiAqKmBmYWNldF93cmFwKClgKiouIEVzdGEgZnVuY2nDs24gcmVwYXJ0ZSBsb3Mgc21hbGwgbXVsdGlwbGVzIGVuIHVuYSByZWppbGxhIGNvbiBmb3JtYSBkZSBtYXRyaXouIAoKYGBge3IsIG91dC53aWR0aCA9ICI5MCUiLCBmaWcuYXNwID0gNC85fQpwICsgZmFjZXRfd3JhcCh2YXJzKFNwZWNpZXMpLCBucm93ID0gMiwgbmNvbCA9IDIpICAgICAgICAjIGdyYWYgeCBmaWxhcyB5IGNvbHVtbmFzCmBgYAoKClNpIGVuIGVsIGRhdGFzZXQgaHViaWVzZW4gZG9zIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgcG9kcsOtYW1vcyBoYWNlciBxdWUgdW5hIGRlIGVsbGFzIHNpcnZpZXNlIHBhcmEgbGxlbmFyIGxhcyBmaWxhcyB5IGxhIG90cmEgbGFzIGNvbHVtbmFzLiAKCgpDb21vIGBpcmlzYCBzw7NsbyB0aWVuZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgKFNwZWNpZXMpIHZhbW9zIGEgZGlzY3JldGl6YXIgdW5hIGRlIGxhcyB2YXJpYWJsZXMgY29udGludWFzLiBQb3IgZWplbXBsbyBsYSBhbmNodXJhIGRlbCBww6l0YWxvLCBjcmVhcmVtb3MgdW5hIG51ZXZhIHZhcmlhYmxlIGRpdmlkaWVuZG8gbGFzIG9ic2VydmFjaW9uZXMgZGUgYFBldGFsLldpZHRoYCBlbiAyIGNhdGVnb3LDrWFzLCBwb3IgZW5jaW1hIHkgcG9yIGRlYmFqbyBkZSBsYSBtZWRpYSBkZSBzdSBtZWRpYS4gQWRlbcOhcyBsbyB2YW1vcyAgYSBoYWNlciBjb24gUi1iYXNlIHkgY29uIGBkcGx5cjo6bnRpbGUoKWAuIFNlZ3VybyBxdWUgaGF5IG1lam9yZXMgZm9ybWFzLCBbcG9yIGVqZW1wbG9dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9hcnVsZXMvdmVyc2lvbnMvMS42LTMvdG9waWNzL2Rpc2NyZXRpemUpCgoKQ29uIGBkcGx5cjo6bnRpbGUoKWAKCmBgYHtyfQppcmlzIDwtIGlyaXMgJT4lIG11dGF0ZShuZXdfdmFyaWFibGUgPSBudGlsZShQZXRhbC5XaWR0aCwgMikpIApgYGAKCkNvbiBSLWJhc2UgeSBsYSBmdW5jacOzbiBgY3V0KClgOgoKYGBge3J9CmlyaXMgPC0gaXJpcwppcmlzJG5ld192YXJpYWJsZSA8LSBjdXQoaXJpcyRQZXRhbC5XaWR0aCwgCiAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKC1JbmYsIG1lYW4oaXJpcyRQZXRhbC5XaWR0aCksIEluZiksIAogICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiZGViYWpvLW1lZGlhIiwgImFycmliYS1tZWRpYSIpKQpgYGAKCgoKQWhvcmEgeWEgdGVuZW1vcyBkb3MgdmFyaWFibGUgZGlzY3JldGEgeSBwb2RlbW9zIGhhY2VyIHF1ZSBgZmFjZXRfZ3JpZCgpYCB1dGlsaWNlIHVuYSB2YXJpYWJsZSBwYXJhIGxsZW5hciBmaWxhcyB5IG90cmEgcGFyYSBjb2x1bW5hcy4gU2UgcHVlZGUgZXNwZWNpZmljYXIgZGUgZG9zIG1hbmVyYXMKCgpgYGB7ciwgb3V0LndpZHRoID0gIjkwJSIsIGZpZy5hc3AgPSA0LzksIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMpICsgZ2VvbV9wb2ludCggYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArCmZhY2V0X2dyaWQocm93cyA9IHZhcnMobmV3X3ZhcmlhYmxlKSwgY29scyA9IHZhcnMoU3BlY2llcykpICAgICAgICAjIGdyYWYgeCBmaWxhcyB5IGNvbHVtbmFzCmBgYAoKCmBgYHtyLCBvdXQud2lkdGggPSAiOTAlIiwgZmlnLmFzcCA9IDQvOX0KZ2dwbG90KGlyaXMpICsgZ2VvbV9wb2ludCggYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArCmZhY2V0X2dyaWQobmV3X3ZhcmlhYmxlIH4gU3BlY2llcykgICAgIApgYGAKCgpDb21vIHZlbW9zLCBsb3MgbGlyaW9zIGRlIGxhIGNsYXNlIHNldG9zYSBzaWVtcHJlIHRpZW5lIGVsIGFuY2hvIGRlIHN1IHDDqXRhbG8gcG9yIGRlYmFqbyBkZSBsYSBtZWRpYSwgeSBsb3MgdmlyZ2luaWNhIHNpZW1wcmUgZXN0w6FuIHBvciBlbmNpbWEgZGUgbGEgbWVkaWEuCgoKIyMjIyBFamVzIGRlIGxvcyBzbWFsbCBtdWx0aXBsZXMKCgpQb2RlbW9zIGFqdXN0YXIgbGFzIGVzY2FsYXMgZGUgbG9zIGVqZXMgcGFyYSBxdWUgc2VhbiBjb211bmVzIHBhcmEgY2FkYSBzbWFsbCBtdWx0aXBsZSAobGEgb3BjacOzbiBwb3IgZGVmZWN0bykgbyBkZWphciBxdWUgbGFzIGVzY2FsYXMgZGUgY2FkYSBncsOhZmljbyB2YXLDrWVuIGVuIGZ1bmNpw7NuIGRlbCByYW5nbyBkZSBsb3MgZGF0b3MgcmVwcmVzZW50YWRvczoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgKyBmYWNldF9ncmlkKHJvd3MgPSB2YXJzKFNwZWNpZXMpKSAgICAjLSBlc2NhbGFzIGNvbXVuZXMKcCArIGZhY2V0X2dyaWQocm93cyA9IHZhcnMoU3BlY2llcyksIHNjYWxlcyA9ICJmcmVlIikgICAjLSBsYXMgZXNjYWxhcyBkZSBjYWRhIHNtYWxsIHB1ZWRlbiB2YXJpYXIKcCArIGZhY2V0X2dyaWQocm93cyA9IHZhcnMoU3BlY2llcyksIHNjYWxlcyA9ICJmcmVlX3kiKSAjLSBzb2xvIGRlamFtb3MgbGlicmUvdmFyaWFyIGxhIGVzY2FsYSBkZWwgZWplIHkKYGBgCgoKU29sbyBtdWVzdHJvIGVsIHJlc3VsdGFkbyBkZSBsYSBzZWd1bmRhIGV4cHJlc2nDs246CgoKYGBge3IsIG91dC53aWR0aCA9ICI5MCUiLCBmaWcuYXNwID0gNC85LCBlY2hvID0gRkFMU0V9CnAgKyBmYWNldF9ncmlkKHJvd3MgPSB2YXJzKFNwZWNpZXMpLCBzY2FsZXMgPSAiZnJlZSIpICAgIy0gbGFzIGVzY2FsYXMgZGUgY2FkYSBzbWFsbCBwdWVkZW4gdmFyaWFyCmBgYAoKCioqVW4gdHJ1cXVpdG86KiogZWwgYXJndW1lbnRvIGBtYXJnaW5gIGVuIGBnZ3Bsb3QyOjpmYWNldF9ncmlkKClgIGHDsWFkZSBtYXJnZW5lcyBhIGxvcyBzbWFsbCBtdWx0aXBsZXMgZmFjaWxpdGFuZG8gbGEgdmlzdWFsaXphY2nDs24uIEFkZW3DoXMgYcOxYWRlIHVuIG51ZXZvIHNtYWxsIGNvbiB0b2RhcyBsYXMgb2JzZXJ2YWNpb25lcy4KCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZmlnLmFzcCA9IDcvOX0KdHdlZXRybWQ6OnR3ZWV0X2VtYmVkKCJodHRwczovL3R3aXR0ZXIuY29tL1dlQXJlUkxhZGllcy9zdGF0dXMvMTEyMzEwOTA5NDQyMjkyMTIxNiIsIHRoZW1lID0gImxpZ2h0IiwgYWxpZ24gPSAiY2VudGVyIiwgbWF4d2lkdGggPSA0MDApCmBgYAoKCgpQdWVkZXMgcHJvYmFybG8gdMO6IG1pc21vIGNvcnJpZW5kbyBsbyBzaWd1aWVudGU6CgpgYGB7ciwgb3V0LndpZHRoID0gIjkwJSIsIGZpZy5hc3AgPSA0Lzl9CnAgKyBmYWNldF9ncmlkKHJvd3MgPSB2YXJzKFNwZWNpZXMpLCAgbWFyZ2lucyA9IFRSVUUpICAgCmBgYAoKCk90cm8gdHJ1Y28sIGVzdGEgdmV6IGF2YW56YWRvOiBVbiBbZ2lzdF0oaHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20vcGFkcGFkcGFkcGFkL2RjMWY0NTIwZTRmOTUzMGIyYzcwZmVkMGU0YTQyNWU5KSBwYXJhIHBvbmVyIGxhYmVscyBhIGxvcyBzbWFsbHMgbXVsdGlwbGVzLgoKPGJyPgoKCiMjIyAzLjQgQW5vdGFjaW9uZXMKCj4gQW5ub3RhdGlvbnMgYXJlIGEgc3BlY2lhbCB0eXBlIG9mIGxheWVyIHRoYXQgZG9u4oCZdCBpbmhlcml0IGdsb2JhbCBzZXR0aW5ncyBmcm9tIHRoZSBwbG90LiBUaGV5IGFyZSB1c2VkIHRvIGFkZCBmaXhlZCByZWZlcmVuY2UgZGF0YSB0byBwbG90cy4KCkxhcyBhbm90YWNpb25lcyBlbiBsb3MgZ3LDoWZpY29zIHBlcm1pdGVuIHJlc2FsdGFyIGFsZ8O6biBmZW7Ds21lbm8gdSBvYnNlcnZhY2nDs24gIGRlIGludGVyw6lzLCB5IHNvbiBpbXBvcnRhbnRlcyBhIGxhIGhvcmEgZGUgY29udGFyIGhpc3RvcmlhcyAoc3Rvcnl0ZWxsaW5nKSBjb24gbG9zIGdyw6FmaWNvcyB5IHZpc3VhbGl6YWNpb25lcy4gCgpFbiBlbCBlbnRvcm5vIGBnZ3Bsb3RgIHBvZGVtb3MgaGFjZXIgYW5vdGFjaW9uZXMgZW4gbnVlc3RybyBncsOhZmljb3MgZGUgdmFyaWFzIG1hbmVyYXMsIHBvciBlamVtcGxvIGNvbiBgYW5ub3RhdGUoKWAuIEF1bnF1ZSBjb25jZXB0dWFsbWVudGUsIGNvbW8gc2XDsWFsYSBIYWRsZXksIGxhcyBhbm90YWNpb25lcyBzb24gbWV0YWRhdG9zLCBkZXNkZSBlbCBwdW50byBkZSB2aXN0YSBwcsOhY3RpY28gc2UgdXNhbiBsb3MgbWlzbWFzIGZ1bmNpb25lcyBvIGdlb21zIHBhcmEgbWFuaXB1bGFybG9zLiAgCgoKVGFtYmnDqW4gZXhpc3RlbiBhbGd1bmFzIGZ1bmNpb25lcyBhdXhpbGlhcmVzIGVuIGdncGxvdDIgeSBlbiBwYXF1ZXRlcyBlc3BlY8OtZmljb3MgcGFyYSBoYWNlciBhbm90YWNpb25lcyBlbiBncsOhZmljb3MgZ2dwbG90LiBQb3IgZWplbXBsbywgY3VhbmRvIHNlIGhhY2VuIGFub3RhY2lvbmVzIGVuIHVuIGdyw6FmaWNvIGRlIHB1bnRvcyBlcyBmw6FjaWwgcXVlIGxhcyBhbm90YWNpb25lcyBjYWlnYW4gdW5hcyBlbmNpbWEgZGUgb3RyYXMsIGVsIHBhcXVldGUgW2dncmVwZWxdKGh0dHBzOi8vYnVmZi5seS8ySFhFcnhMKSBwZXJtaXRlIGFsaXZpYXIgZXN0ZSBwcm9ibGVtYS4KClV0aWxpY2Vtb3MgbGEgZnVuY2nDs24gYGFubm90YXRlKClgLiBQb3IgZWplbXBsbywgZWwgc2lndWllbnRlIGNodW5rIGhhY2UgYWxndW5hcyBhbm90YWNpb25lcyBzaW4gbXVjaG8gc2VudGlkbyBwZXJvIGbDoWNpbGVzIGRlIGVudGVuZGVyOgoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyBnZW9tX3BvaW50KCkKYGBgCgpQb3IgZWplbXBsbywgZWwgc2lndWllbnRlIGNodW5rIHVzYSBgYW5ub3RhdGUoKWAgcGFyYSBoYWNlciBhbGd1bmFzIGFub3RhY2lvbmVzIHNpbiBtdWNobyBzZW50aWRvLCBwZXJvIGbDoWNpbGVzIGRlIGVudGVuZGVyOgoKYGBge3J9CnAgKyBhbm5vdGF0ZShnZW9tID0gInRleHQiLCB4ID0gNiwgeSA9IDIsIGxhYmVsID0gIlVuYSBhbm90YWNpw7NuIiwgc2l6ZSA9IDUpICsKICAgIGFubm90YXRlKCJyZWN0IiwgeG1pbiA9IDYsIHhtYXggPSA3LHltaW4gPSAtSW5mLCB5bWF4ID0gSW5mLCBhbHBoYSA9IDAuMiwgZmlsbCA9ICJwaW5rIikgKyAKICAgIGFubm90YXRlKCJzZWdtZW50IiwgeCA9IDUsIHhlbmQgPSA3LCB5ID0gNiwgeWVuZCA9IDgsIGNvbG91ciA9ICJibHVlIikgCmBgYAoKPGJyPgoKIyMjIyBBbm90YWNpb25lcyBkZSB0ZXh0byBlbiBsYXMgb2JzZXJ2YWNpb25lcwoKQWdyZWdhciB0ZXh0byBhIHVuIGdyw6FmaWNvIGVzIHVuYSBkZSBsYXMgZm9ybWFzIG3DoXMgY29tdW5lcyBkZSBhbm90YWNpw7NuLiBQb3IgZWplbXBsbywgcGFyYSBzZcOxYWxpemFyIGUgaWRlbnRpZmljYXIgb2JzZXJ2YWNpb25lcyBhbsOzbWFsYXMuIFNpbiBlbWJhcmdvLCBjb21vIHNlw7FhbGEgSGFkbGV5LCBhw7FhZGlyIHRleHRvIG5vIGVzIGbDoWNpbCBwb3IgbGEgZm9ybWEgZW4gbGEgcXVlIFIgbWFuZWphIGxhcyBmdWVudGVzLgoKTGEgZnVuY2nDs24gcHJpbmNpcGFsIHBhcmEgZWwgZXRpcXVldGFkbyBkZSBncsOhZmljb3MgZXMgYGdlb21fdGV4dCgpYC4gUG9yIGVqZW1wbG86CgoKYGBge3IsIGV2YWwgPSBUUlVFLCBvdXQud2lkdGggPSAiNjAlIn0KcCArIGdlb21fdGV4dChhZXMobGFiZWwgPSBTcGVjaWVzKSkKYGBgCgpUYW1iacOpbiBwb2TDrWFtb3MgaGFiZXIgYcOxYWRpZG8gZWwgdmFsb3IgZGUgbGEgbG9uZ2l0dWQgZGVsIHDDqXRhbG8uIAoKCmBgYHtyLCBldmFsID0gVFJVRSwgb3V0LndpZHRoID0gIjYwJSJ9CnAgKyBnZW9tX3RleHQoYWVzKGxhYmVsID0gUGV0YWwuTGVuZ3RoKSkKYGBgCgoKCkhlbW9zIGHDsWFkaWRvIGEgY2FkYSBvYnNlcnZhY2nDs24gdW4gdGV4dG8sIHByb3ZlbmllbnRlIGRlIGFsZ3VuYSBkZSBsYXMgdmFyaWFibGVzIGRlIGBpcmlzYC4gRXN0ZSBncsOhZmljbyBubyBlcyBtdXkgw7p0aWwsIHBlcm8gbGEgdMOpY25pY2Egc8OtLiBJbWFnaW5hIHF1ZSBxdWVyZW1vcyBtYXJjYXIgbG9zIGxpcmlvcyA0NSB5IDE0MF5bTyBtYXJjYXIvYW5vdGFyIGxvcyBsaXJpb3MgMiBsaXJpb3MgbcOhcyBncmFuZGVzIG8gZWwgbGlyaW8gbWVkaWFuby4gwr9MbyBoYWPDqWlzP10uIFBvZGVtb3MgaGFjZXIgbG8gc2lndWllbnRlOgoKCmBgYHtyLCBldmFsID0gVFJVRSwgb3V0LndpZHRoID0gIjYwJSJ9CiMtIHNlbGVjY2lvbmFtb3MgbG9zIGxpcmlvcyBtw6FzIGdyYW5kZXMgZGUgY2FkYSBlc3BlY2llIAppcmlzX21heCA8LSBpcmlzICU+JSBncm91cF9ieShTcGVjaWVzKSAlPiUgc2xpY2VfbWF4KFBldGFsLkxlbmd0aCwgbiA9IDEpCgpwICsgCiBnZW9tX3RleHQoZGF0YSA9IGlyaXNfbWF4LCBhZXMobGFiZWwgPSBTcGVjaWVzKSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMykKYGBgCgpQb2RlbW9zIGFqdXN0YXIgbGEgcG9zaWNpw7NuIHkgdGFtYcOxbyBkZWwgdGV4dG8sIGV0Yy4uIFBvciBlamVtcGxvLCBwb2RlbW9zIGNhbWJpYXIgbGEgYWxpbmVhY2nDs24gZGUgbGFzIGFub3RhY2lvbmVzIGNvbiBjb24gYGhqdXN0KOKAnGxlZnTigJ0sIOKAnGNlbnRlcuKAnSwg4oCccmlnaHTigJ0sIOKAnGlud2FyZOKAnSwg4oCcb3V0d2FyZOKAnSlgIHkgYHZqdXN0ICjigJxib3R0b23igJ0sIOKAnG1pZGRsZeKAnSwg4oCcdG9w4oCdLCDigJxpbndhcmTigJ0sIOKAnG91dHdhcmTigJ0pYC4KCjxicj4KCgojIyMjIExpbmVhcwoKUG9kZW1vcyBhw7FhZGlyIGxpbmVhczogCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNikKcCArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDUsIHNpemUgPSAxLjcsIGNvbG91ciA9ICJwdXJwbGUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKQpwICsgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMC43LCBzbG9wZSA9IDAuNCwgc2l6ZSA9IDEuOSwgY29sb3VyID0gInN0ZWVsYmx1ZSIpCmBgYAoKPGJyPgoKCiMjIyAzLjUgQ2FtYmlhbmRvIGxvcyBsw61taXRlcyBkZSBsb3MgZWplcwoKICBTaSBxdWllcmVzIG1vZGlmaWNhciBlbCByZWNvcnJpZG8gZGUgbG9zIGVqZXMsIGxvcyAibMOtbWl0ZXMiIGRlIGxvcyBlamVzLCBwdWVkZXMgdXNhciBgbGltcygpYC4gUGFyYSBsb3MgZWplcyBYIGUgWSBoYXkgZG9zIGZ1bmNpb25lcyBhdXhpbGlhcmVzOiBgeGxpbSgpYCBlIGB5bGltKClgLiAKCgpgYGB7ciwgb3V0LndpZHRoID0gIjYwJSJ9CnAgKyBsaW1zKGNvbG9yID0gYygic2V0b3NhIiksIHggPSBjKE5BLDYpLCB5ID0gYygxLDgpKQpgYGAKCgoKYGBge3IsIG91dC53aWR0aCA9ICI2MCUifQpwICsgeGxpbShjICg0LCA2KSkgKyB5bGltKGMoTkEsIDUpKSAKYGBgCgpTZSBwdWVkZSBoYXN0YSBkYXIgbGEgdnVlbHRhIGEgbG9zIGVqZXMKCgpgYGB7ciwgb3V0LndpZHRoID0gIjYwJSJ9CnAgKyB4bGltKGMgKDcsIDMpKSArIHlsaW0oYyhOQSwgNSkpIApgYGAKCgpMb3MgbMOtbWl0ZXMgbyBkb21pbmlvIGRlbCBncsOhZmljbyBzdWVsZW4gb2J0ZW5lcnNlIGF1dG9tw6F0aWNhbWVudGUgZGUgbG9zIGRhdG9zLCBwZXJvLCBvdHJhIHZleiBhY2NvcmRpbmcgdG8gSGFkbGV5LCBoYXkgZG9zIHJhem9uZXMgcG9yIGxhcyBxdWUgcG9kZW1vcyBlc3RhciBpbnRlcmVzYWRvcyBlbiBjYW1iaWFyIGxvcyBsw61taXRlcyBkZWwgZ3LDoWZpY286IAoKICAxKSBjZW50cmFybm9zIGVuIHVuYSByZWdpw7NuIGVzcGVjaWZpY2EgZGVsIGdyw6FmaWNvIAogIAogIDIpIGF1bWVudGFyIGxvcyBsw61taXRlcyBwYXJhIHF1ZSB2YXJpb3MgZ3LDoWZpY29zIGFqdXN0ZW4gc3VzIGVzY2FsYXMuICAgCgo8YnI+CgpQb3IgZWplbXBsbywgc2kgZGVzcHXDqXMgZGUgaGFjZXIgdW4gZ3LDoWZpY28gcXVpZXJlcyBjZW50cmFydGUgc8OzbG8gZW4gdW5hIHBhcnRlOyBlcyBkZWNpciwgaGFjZXIgdW4gem9vbSBzb2JyZSB1bmEgcGFydGUgZGVsIGdyw6FmaWNvLCB0ZW5lbW9zIDIgYWx0ZXJuYXRpdmFzOgoKMS4gQm9ycmFyIGxvcyBwdW50b3MgcXVlIGNhZW4gZnVlcmEgZGUgbG9zIGxpbWl0ZXMgZGUgbG8gcXVlIHF1aWVyYXMgcXVlIHNlIHZpc3VhbGljZSAoc2kgZW4gdW5hIGVzY2FsYSBjb250aW51YSBzb2xvIHF1aWVyZXMgdXNhciB1biBsw61taXRlIHBvbiBOQSk6CgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCArIHhsaW0oYyg0LCA1KSkgKyB5bGltKGMoTkEsIDUpKSAjLSBjdWlkYWRvLCBzZSBwdWVkZW4gYm9ycmFyIG9ic2VydmFjaW9uZXMKYGBgCgpDb24gZXN0ZSBlbmZvcXVlIHRpZW5lcyBxdWUgdGVuZXIgY3VpZGFkbywgeWEgcXVlICBzaSBwb3IgZWplbXBsbyBkZXNwdcOpcyB1dGlsaXphciBhbGd1bmEgdHJhbnNmb3JtYWNpw7NuIGVzdGFkw61zdGljYSBjb21vIHBvciBlamVtcGxvIGBnZW9tX3Ntb290aCgpYCwgbGFzIG9ic2VydmFjaW9uZXMgZWxpbWluYWRhcyBhbCBhanVzdGFyIGxvcyBsw61taXRlcyBubyBlbnRyYXLDoW4gZW4gZWwgY8OhbGN1bG8gZXN0YWTDrXN0aWNvLiAKCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCArIGdlb21fc21vb3RoKGNvbG9yID0gInB1cnBsZSIpCnAgKyBnZW9tX3Ntb290aChjb2xvciA9ICJwdXJwbGUiKSArIHhsaW0oYyg0LCA1LjcpKSArIHlsaW0oYygxLjUsIDUpKSAgICMgZGVsZXRlcyBwb2ludHMKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZmlnLmFzcCA9IDUvOSwgb3V0LndpZHRoID0gIjkwJSJ9CnAxIDwtIHAgKyBnZW9tX3Ntb290aChjb2xvciA9ICJwdXJwbGUiKQpwMiA8LSBwICsgZ2VvbV9zbW9vdGgoY29sb3IgPSAicHVycGxlIikgKyB4bGltKGMoNCwgNS43KSkgKyB5bGltKGMoMS41LCA1KSkgICAjIGRlbGV0ZXMgcG9pbnRzCgoKcDEgKyBwMiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKCgoyLiBDYW1iaWFyIGxvcyBsw61taXRlcyBkZSBsb3MgZWplcyBYIGUgWSBoYWNpZW5kbyB1biB6b29tIGVuIGxhIHJlZ2nDs24gZGUgaW50ZXLDqXMgcGVybyBzaW4gZWxpbWluYXIgcHVudG9zLiBFc3RvIGxvIGNvbnNlZ3VpbW9zIGNvbiBgY29vcmRfY2FydGVzaWFuKClgLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCArIGdlb21fc21vb3RoKGNvbG9yID0gInB1cnBsZSIpCnAgKyBnZW9tX3Ntb290aChjb2xvciA9ICJwdXJwbGUiKSArIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYyg0LCA1LjcpLCB5bGltID0gYygxLjUsIDUpKQpgYGAKCgoKCmBgYHtyLCBlY2hvID0gRkFMU0UsIGZpZy5hc3AgPSA1LzksIG91dC53aWR0aCA9ICI5MCUifQpwMSA8LSBwICsgZ2VvbV9zbW9vdGgoY29sb3IgPSAicHVycGxlIikKcDIgPC0gcCArIGdlb21fc21vb3RoKGNvbG9yID0gInB1cnBsZSIpICsgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDQsIDUuNyksIHlsaW0gPSBjKDEuNSwgNSkpCgoKcDEgKyBwMiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKCgo8YnI+CgoKIyMjIDMuNiBFc2NhbGFzCgo+IFNjYWxlcyBjb250cm9sIHRoZSBkZXRhaWxzIG9mIGhvdyBkYXRhIHZhbHVlcyBhcmUgdHJhbnNsYXRlZCB0byB2aXN1YWwgcHJvcGVydGllcy4gT3ZlcnJpZGUgdGhlIGRlZmF1bHQgc2NhbGVzIHRvIHR3ZWFrIGRldGFpbHMgbGlrZSB0aGUgYXhpcyBsYWJlbHMgb3IgbGVnZW5kIGtleXMsIG9yIHRvIHVzZSBhIGNvbXBsZXRlbHkgZGlmZmVyZW50IHRyYW5zbGF0aW9uIGZyb20gZGF0YSB0byBhZXN0aGV0aWMuIGxhYnMoKSBhbmQgbGltcygpIGFyZSBjb252ZW5pZW50IGhlbHBlcnMgZm9yIHRoZSBtb3N0IGNvbW1vbiBhZGp1c3RtZW50cyB0byB0aGUgbGFiZWxzIGFuZCBsaW1pdHMuCgpMYXMgZXNjYWxhcyBwZXJtaXRlbiBsZWVyL2ludGVycHJldGFyIHVuIGdyw6FmaWNvOyBwZXJtaXRlbiBpbnRlcnByZXRhciBsb3MgZWxlbWVudG9zIGdlb23DqXRyaWNvcyAocG9yIGVqZW1wbG8gZW4gbnVlc3RybyBncsOhZmljbywgbG9zIHB1bnRvcykgZW4gZnVuY2nDs24gZGUgbG9zIHZhbG9yZXMgb3JpZ2luYWxlcyBkZSBsYXMgb2JzZXJ2YWNpb25lcy4gTGFzIGVzY2FsYXMgc29uIHVuIGVsZW1lbnRvIG3DoXMgZGUgbG9zIGdyw6FmaWNvcyBnZ3Bsb3QgeSBzZSBwcm9kdWNlbi9jb250cm9sYW4gY29uIGxhIGZhbWlsaWEgZGUgZnVuY2lvbmVzIGBzY2FsZV94eCgpYAoKRW4gYGdncGxvdDJgIGxhcyBlc2NhbGFzIG8gZ3XDrWFzIHNlIHByb2R1Y2VuIGF1dG9tw6F0aWNhbWVudGUsIG5vIHZlbW9zIHF1ZSBoYWdhbW9zIG5hZGEsIHBlcm8gdW5kZXIgdGhlIGhvb2Qgc2UgZXN0w6FuIGZpamFuZG8gY29uIGxhIGZhbWlsaWEgZGUgZnVuY2lvbmVzIGBzY2FsZV94eCgpYCBxdWUgc29uIGxhcyBxdWUgY29udHJvbGFuIGNvbW8gc2UgbWFwZWFuIGxvcyB2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgY29uIGxhcyBwcm9waWVkYWRlcyBlc3TDqXRpY2FzIGRlIG51ZXN0cm8gZ3LDoWZpY28gKHBvciBlamVtcGxvIGVsIGVqZSBYKSBkZSBmb3JtYSBxdWUgcG9kYW1vcyBpbnRlcnByZXRhciBsYSBwb3NpY2nDs24gZGUgbG9zIGRpc3RpbnRvcyBwdW50b3MgbWlyYW5kbyBsYXMgZXNjYWxhcy4gTGFzIGVzY2FsYXMgdGFtYmnDqW4gY29uc3RydXllbiBsb3MgZWxlbWVudG9zIHF1ZSBwZXJtaXRlbiBsZWVyL2ludGVycHJldGFyIGxvcyBncsOhZmljb3M6IGxvcyBlamVzIHkgbGFzIGxleWVuZGFzLgoKQ29tbyBgZ2dwbG90MmAgaGFjZSBlbCBtYXBlbyB5IGdlbmVyYSBsYXMgZXNjYWxhcyB5IGxleWVuZGFzIGF1dG9tw6F0aWNhbWVudGUsIGVuIGxhIHByw6FjdGljYSBwb2RlbW9zIGhhY2VyIGdyw6FmaWNvcyBzaW4gc2FiZXIgY29tbyBmdW5jaW9uYW4geSwgcG9yIHRhbnRvLCBzaW4gc2FiZXIgbWFuaXB1bGFyIGVzdGUgZWxlbWVudG8gZGUgdW4gZ3LDoWZpY28gZ2dwbG90LiBQZXJvIHNpIGFwcmVuZGVtb3MgYSBtYW5pcHVsYXIgbGFzIGVzY2FsYXMsIGVzdG8gbm9zIGRhcsOhIG3DoXMgZmxleGliaWxpZGFkIGEgbGEgaG9yYSBkZSB1dGlsaXphciBgZ2dwbG90MmAuCgpFbiBtdWNob3MgdGlwb3MgZGUgZGF0b3MgZXMgaW1wb3J0YW50ZSBwYXJhcnNlIGEgcGVuc2FyIGN1YWwgZXMgbGEgbWVqb3IgZXNjYWxhIHBhcmEgcmVwcmVzZW50YXIgbGFzIHZhcmlhYmxlcy4gUXVpesOhcyBzZWEgY29udmVuaWVudGUgY2FtYmlhciBsYSBlc2NhbGEgZGUgdW4gZWplIHBhcmEgZGlzdHJpYnVpciBtZWpvciBsYXMgb2JzZXJ2YWNpb25lcyBlbiBlbCBlc3BhY2lvLCBvIHBhcmEgaW50ZXJwcmV0YXIgbWVqb3IgbGFzIHZhcmlhY2lvbmVzIGVudHJlIG9ic2VydmFjaW9uZXM7IHBvciBlamVtcGxvIGxhIGVzY2FsYSBsb2dhcsOtdG1pY2EgbyBlbiBwb3JjZW50YWplcyBzb24gYSB2ZWNlcyBtw6FzIGFwcm9waWFkYXMgcXVlIGxhcyBlc2NhbGFzIG9yaWdpbmFsZXMuCgoKRW4gcmVhbGlkYWQsIHBhcmEgY2FkYSBwYXIgdmFyaWFibGUvZXN0w6l0aWNhIHJlcHJlc2VudGFkYSBlbiB1biBncsOhZmljbyBnZ3Bsb3QgZXMgbmVjZXNhcmlhIHVuYSBlc2NhbGEsIHkgdGVuZHLDrWEgcXVlIGZpamFyc2UgY29uIHVuYSBkZSBsYXMgZnVuY2lvbmVzIGRlIGxhIGZhbWlsaWEgYHNjYWxlX3h4KClgLiBSZWFsbWVudGUgY3VhbmRvIGhhY2Vtb3MgZXN0ZSBncsOhZmljbywgZW4gZWwgcXVlIGFzb2NpYW1vcyAzIHZhcmlhYmxlcyBhIDMgcHJvcGllZGFkZXMgZXN0w6l0aWNhcyBjb24gYGFlcygpYCwgc2UgbmVjZXNpdGFyw61hIGVzcGVjaWZpY2FyIGxhcyBlc2NhbGFzIGRlIGxhcyAzIHZhcmlhYmxlczoKCgpgYGB7cn0KcCA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyAgZ2VvbV9wb2ludChhZXMgKGNvbG9yID0gU3BlY2llcykpIApgYGAKCkJpZW4sIHBlcm8gZW50b25jZXMgwr9wb3IgcXXDqSBubyBsbyBoYWNlbW9zPywgwr9wb3IgcXXDqSBubyBlc3BlY2lmaWNhbW9zIGxhcyBlc2NhbGFzPyBQdWVzIHBvcnF1ZSBsbyBoYWNlIGBnZ3Bsb3QyYCBwb3Igbm9zb3Ryb3MuIEVuIHJlYWxpZGFkIGN1YW5kbyBlamVjdXRhbW9zIGxhIGV4cHJlc2nDs24gYW50ZXJpb3IsIHJlYWxtZW50ZSBzZSBlc3TDoSBoYWNpZW5kbyBsbyBzaWd1aWVudGU6CgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgICBnZW9tX3BvaW50KGFlcyAoY29sb3IgPSBTcGVjaWVzKSkgKwogIHNjYWxlX3hfY29udGludW91cygpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKCkgKyAKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZSgpCmBgYAoKRXMgZGVjaXIsIGBnZ3Bsb3QyYCBhc2lnbmEgYSBjYWRhIHZhcmlhYmxlIHVuYSBlc2NhbGEsIGEgbGFzIHZhcmlhYmxlcyBjb250aW51YXMgbGVzIGFzaWduYSB1bmEgZXNjYWxhIGNvbnRpbnVhIHkgYSBsYXMgY2F0ZWfDs3JpY2FzIHVuYSBlc2NhbGEgZGlzY3JldGEuIFBFUk8sIHNpIHF1ZXJlbW9zLCBzaSBsbyBjb25zaWRlcmFtb3MgYXByb3BpYWRvIHBvZHLDrWFtb3MgY2FtYmlhciBsYSBlc2NhbGEuIFBvciBlamVtcGxvOgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgKyBzY2FsZV95X3JldmVyc2UoKSArIHNjYWxlX2NvbG91cl9ncmV5KCkKcCArIHNjYWxlX3hfc3FydCgpICsgc2NhbGVfeV9sb2cxMCgpCnAgKyBzY2FsZV94X2NvbnRpbnVvdXModHJhbnMgPSAibG9nIikKcCArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikKYGBgCgpTb2xvIHJlcHJlc2VudG8gbGEgcHJpbWVyYSB0cmFuc2Zvcm1hY2nDs24sIGVuIGxhIHF1ZSBzZSBkYSBsYSB2dWVsdGEgYSBsYSBlc2NhbGEgZGVsIGVqZSBZIHkgZWwgY29sb3IsIGFzb2NpYWRvIGEgbGEgdmFyaWFibGUgU3BlY2llcywgcGFzYSBhIGVzY2FsYSBkZSBncmlzZXM6CgpgYGB7ciwgZWNobyA9IEZBTFNFLCBmaWcuYXNwID0gNS85LCBvdXQud2lkdGggPSAiNjAlIn0KcCArIHNjYWxlX3lfcmV2ZXJzZSgpICsgc2NhbGVfY29sb3VyX2dyZXkoKQpgYGAKCjxicj4KCiMjIyMgTGFiZWxzIGRlIGxvcyBlamVzCgpUYW1iacOpbiBzZSBwdWVkZW4gbW9kaWZpY2FyIGxvIHF1ZSB2ZW1vcyBlbiBsb3MgZXNjYWxhcy4gQW50ZXMgdmFtb3MgYSBtb2RpZmljYXIgbG9zIHTDrXR1bG9zIGRlIGxvcyBlamVzIHkgbGV5ZW5kYXMuCgoKYGBge3IsIGV2YWwgPSBUUlVFfQpwIDwtIHAgKyBsYWJzKHggPSAiRWplIFgiLCB5ID0gIkVqZSBZIiwgY29sb3IgPSAiTGV5ZW5kYVxuIHBhcmEgZWwgY29sb3IiKQpgYGAKCgpEZSBtb21lbnRvIGxhIGVzY2FsYSBkZWwgZWplIFggdmFyaWEgYXByb3hpbWFkYW1lbnRlIGRlIDMgYSA4LiBMb3MgdMOtdHVsb3MgZGUgbGEgZXNjYWxhIGRlbCBlamUgWCBzw7NsbyBtdWVzdHJhIGxvcyB2YWxvcmVzIDUsIDYsIDcgeSA4LiBWYW1vcyBhIG1vZGlmaWNhciAiZWwgdGV4dG8iLCBsb3MgbsO6bWVyb3MgcXVlIHNlIHZlbiB5IHF1ZSBzaXJ2ZW4gZGUgZ3XDrWEgcGFyYSBsYSBlc2NhbGEgZGVsIGVqZSBYOgoKYGBge3IsIGV2YWwgPSBUUlVFLCBvdXQud2lkdGggPSAiNDUlIn0KcCArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMywgMTAsIDAuNSksIGxpbWl0cyA9IGMoMywgMTApKSAKYGBgCgoKRW4gZWwgZWplIFkgc2UgdmVuIGxvcyBuw7ptZXJvcyAyLCA0IHkgNi4gQ2FtYmllbW9zIGxvcyBsYWJlbHMgZGUgc3UgZXNjYWxhIChubyBkZWwgZWplIFksIHNpbm8gZGUgbGEgZXNjYWxhIGRlbCBlamUgWSk6CgoKYGBge3IsIGV2YWwgPSBUUlVFLCBvdXQud2lkdGggPSAiNDUlIn0KcCArIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTIsIDAuMjUpLCAgbGFiZWwgPSBzY2FsZXM6OmRvbGxhcikgCmBgYAoKCgpUYW1iacOpbiBzZSBwdWVkZW4gbW9kaWZpY2FyIGxvcyBkZSBsYSBsZXllbmRhIHBhcmEgZWwgY29sb3I6CgoKYGBge3IsIGV2YWwgPSBUUlVFLCBvdXQud2lkdGggPSAiNDUlIn0KcCArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJwdXJwbGUiLCAicGluayIsICJyZWQyIiksIG5hbWUgPSAiRXNwZWNpZXNcbiBkZSBsaXJpb3MiKQpgYGAKClNpIGh1Ymllc2UgdXNhZG8gdW5hIHZhcmlhYmxlIGRpc2NyZXRhIGFzb2NpYWRhICBhIGxhIGVzdMOpdGljYSAic2hhcGUiLCB0ZW5kcsOtYW1vcyBxdWUgdXNhciBgc2NhbGVfc2hhcGVfZGlzY3JldGUoKWAsIHNpIGxhIHZhcmlhYmxlIGFzb2NpYWRhIGEgc2hhcGUgZnVlc2UgY29udGludWE6IGBzY2FsZV9zaGFwZV9jb250aW51b3VzKClgCgrCv1kgc2kgaHViacOpc2Vtb3MgdXNhZG8gdW5hIHZhcmlhYmxlIGNvbnRpbnVhIHBhcmEgbGEgZXN0w6l0aWNhICJmaWxsIj8gUHVlcyBgc2NhbGVfZmlsbF9jb250aW51b3VzKClgCgo8YnI+CgojIyMjIEZhbWlsaWEgZGUgZnVuY2lvbmVzIGRlIGVzY2FsYQoKICDigKIgc2NhbGVfKl9jb250aW51b3VzKCkgIAogIOKAoiBzY2FsZV8qX2Rpc2NyZXRlKCkgIAogIOKAoiBzY2FsZV8qX29yZGluYWwoKSAgCiAg4oCiIHNjYWxlXypfbWFudWFsKCkgIAogIOKAoiBzY2FsZV97Y29sb3IvZmlsbH1fYnJld2VyKCkgIAogIOKAoiBzY2FsZV97Y29sb3IvZmlsbH1fZGlzdGlsbGVyKCkgIAogIOKAoiBzY2FsZV97Y29sb3IvZmlsbH1fZ3JhZGllbnQoKSAgCgoKPGJyPgoKIyMjIDMuNyBTdGF0cyAodHJhbnNmb3JtYWNpb25lcyBlc3RhZMOtc3RpY2FzKQoKClB1ZWRlIGxlZXJzZSBlbiBsYSB3ZWIgZGUgYGdncGxvdDJgLCBjb25jcmV0YW1lbnRlIFthcXXDrV0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlLykgbG8gc2lndWllbnRlOgoKPiBBIGxheWVyIGNvbWJpbmVzIGRhdGEsIGFlc3RoZXRpYyBtYXBwaW5nLCBhIGdlb20gKGdlb21ldHJpYyBvYmplY3QpLCBhIHN0YXQgKHN0YXRpc3RpY2FsIHRyYW5zZm9ybWF0aW9uKSwgYW5kIGEgcG9zaXRpb24gYWRqdXN0bWVudC4gVHlwaWNhbGx5LCB5b3Ugd2lsbCBjcmVhdGUgbGF5ZXJzIHVzaW5nIGEgZ2VvbV8gZnVuY3Rpb24gCgpQRVJPCgo+IEEgaGFuZGZ1bCBvZiBsYXllcnMgYXJlIG1vcmUgZWFzaWx5IHNwZWNpZmllZCB3aXRoIGEgc3RhdF8gZnVuY3Rpb24sIGRyYXdpbmcgYXR0ZW50aW9uIHRvIHRoZSBzdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbiByYXRoZXIgdGhhbiB0aGUgdmlzdWFsIGFwcGVhcmFuY2UuIFRoZSBjb21wdXRlZCB2YXJpYWJsZXMgY2FuIGJlIG1hcHBlZCB1c2luZyBzdGF0KCkuCgpBbGd1bm9zIGdyw6FmaWNvcywgY29tbyBsb3MgZ3LDoWZpY29zIGRlIHB1bnRvcywgbm8gcmVxdWllcmVuIGRlbCB1c28gZGUgdHJhbnNmb3JtYWNpb25lcyBlc3RhZMOtc3RpY2FzIGRlIGxhcyBvYnNlcnZhY2lvbmVzLCBwZXJvIG90cm9zIGdyw6FmaWNvcyBjb21vIHJlY3RhcyBvIGN1cnZhcyBkZSBwcmVkaWNjacOzbiBvIGNvbW8gbG9zIGJveHBsb3RzIG8gZGlhZ3JhbWFzIGRlIGNhamEsIHPDrSBxdWUgbGFzIG5lY2VzaXRhbiBeW1BvciBlamVtcGxvLCBwaWVuc2EgcXVlIGxvIHF1ZSBzZSB2aXN1YWxpemEgY29uIGBnZW9tX3Ntb3RoKClgIG5vIHNvbiBsb3MgZGF0b3Mgb3JpZ2luYWxlcyBzaW5vLCBwb3IgZWplbXBsbywgbGEgbGluZWEgZGUgcmVncmVzacOzbiBzaSB1c2Ftb3MgYGdlb21fc21vdGgobWV0aG9kID0gImxtIilgXS4gUG9kZW1vcyB1c2FyIHRyYW5zZm9ybWFjaW9uZXMgZXN0YWTDrXN0aWNhcyBlbiBncsOhZmljb3MgZ2dwbG90IGNvbiBsYSBmYW1pbGlhIGRlIGZ1bmNpb25lcyAqKmBzdGF0X3h4KClgKioKClBvciBlamVtcGxvLCBjdWFuZG8gc2UgaGFjZSB1biBkaWFncmFtYSBkZSBjYWphIG8gYm94cGxvdCwgbm8gc2UgcmVwcmVzZW50YW4gbGFzIG9ic2VydmFjaW9uZXMgb3JpZ2luYWxlcywgc2lubyBxdWUgc2UgbXVlc3RyYW4gNSBlc3RhZMOtc3RpY29zIHJlc3VtZW4gZGUgbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgZGF0b3M7IGVzIGRlY2lyLCBzZSB1dGlsaXphIHVuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhLiBDdWFuZG8gdXPDoWJhbW9zIGBnZW9tX3Ntb3RoKClgIHRhbXBvY28gcmVwcmVzZW50w6FiYW1vcyBjb24gw6lsIGxvcyBkYXRvcyBvcmlnaW5hbGVzLCBzaW5vIHVuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIGRlIGVzdG9zLiBDb25jcmV0YW1lbnRlIGxhIHRyYW5zZm9ybWFjacOzbiBlc3RhZMOtc3RpY2EgcXVlIHV0aWxpemEgYGdlb21fc21vdGgoKWAgZXMgZ2Vuw6lyaWNhbWVudGUgdW4gInNtb290aGVyIiwgY2FsY3VsYSBtZWRpYW50ZSB1bmEgcm9sbGluZy13aW5kb3dzIGxhIG1lZGlhIGRlIHksIGNvbmRpY2lvbmFkYSBhIHguICAKCkNhZGEgZnVuY2nDs24gYGdlb21feHgoKWAgcXVlIHV0aWxpY2Vtb3MsIGVuIHJlYWxpZGFkIG5lY2VzaXRhIGRlIHVuIHN0YXRfeHgoKSwgZW50b25jZXMgwr9wb3IgcXXDqSBudW5jYSBsbyBoZW1vcyB1c2Fkby9lc3BlY2lmaWNhZG8gbm9zb3Ryb3M/IENvbiBgZ2dwbG90MmAgbGEgcmF6w7NuIGVzIGNhc2kgc2llbXByZSBsYSBtaXNtYTogcG9ycXVlIHVuZGVyIHRoZSBob29kIGBnZ3Bsb3QyYCBoYWNlIG11Y2hhcyBjb3NhcyBwb3Igbm9zb3Ryb3MuIEVuIGNvbmNyZXRvLCBjYWRhIHZleiBxdWUgdXNhbW9zIHVuIGBnZW9tX3h4KClgIGVuIHJlYWxpZGFkIGBnZ3Bsb3QyYCBlc3TDoSBmaWphbmRvIHVuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIHBvciBkZWZlY3RvIHBvciBub3NvdHJvcy4gR2dwbG90IGVzIHVuIHNpc3RlbWEgbXV5IGNvbXBsZXRvLCBwZXJvIGHDum4gYXPDrSwgdW5hIHZleiBsbyBlbnRpZW5kZXMsIGhhY2VyIGdyw6FmaWNvcyBjb24gZWwgZXMgcmVsYXRpdmFtZW50ZSBmw6FjaWwgeSByw6FwaWRvIHBvcnF1ZSBtdWNoYXMgZGUgc3VzIG9wY2lvbmVzIG9wY2lvbmVzIG5vIGhhY2UgZmFsdGEgZXNwZWNpZmljYXJsYXMgZGlyZWN0YW1lbnRlLgoKClBvciBlamVtcGxvLCDCv2N1YWwgZXMgbGEgdHJhbnNmb3JtYWNpw7NuIGVzdGFkw61zdGljYSBxdWUgc2UgdXNhIHBvciBkZWZlY3RvIGVuIGdlb21fcG9pbnQoKT8gTmluZ3VuYSwgYnVlbm8sIGVuIHJlYWxpZGFkIHVzYSAgYHN0YXQgPSAiaWRlbnRpdHkiYCwgcGVybyBjb21vIGxvIGVzcGVjaWZpY2EgZ2dwbG90MiBhdXRvbcOhdGljYW1lbnRlLCBub3NvdHJvcyBubyBub3MgZGFtb3MgY3VlbnRhLgoKQ3VhbmRvIGhhY8OtYW1vcyBlc3RlIGdyw6FmaWNvOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMsIGFlcyhQZXRhbC5MZW5ndGgsIFNlcGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpIApgYGAKCkVuIHJlYWxpZGFkIGVzdMOhYmFtb3MgaGFjaWVuZG8KCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFBldGFsLkxlbmd0aCwgU2VwYWwuTGVuZ3RoKSkgKyBnZW9tX3BvaW50KHN0YXQgPSAiaWRlbnRpdHkiKQpgYGAKCsK/UXXDqSB0cmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgcG9kZW1vcyBoYWNlciBjdWFuZG8gdXNlbW9zIGBnZW9tX3BvaW50KClgPyBQb3IgZWplbXBsbywgYGdlb21fcG9pbnQoc3RhdCA9ICJ1bmlxdWUiKWAgc8OzbG8gcmVwcmVzZW50YXLDrWEgbGFzIG9ic2VydmFjaW9uZXMgw7puaWNhcyBvIE5PIHJlcGV0aWRhcy4gRW4gZXN0ZSBjYXNvIGNyZW8gcXVlIGlyaXMgbm8gdGllbmUgb2JzZXJ2YWNpb25lcyByZXBldGlkYXMsIGFzw60gc2kgZWplY3V0w6FpcyBsYSBpbnN0cnVjY2nDs24gZGUgYWJham8gc2Ugc2VndWlyw6FuIHZpc3VhbGl6YW5kbyBsb3MgMTUwIGxpcmlvcy4KCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChpcmlzLCBhZXMoUGV0YWwuTGVuZ3RoLCBTZXBhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoc3RhdCA9ICJ1bmlxdWUiKSAjLSBkZWphcsOtYSBzb2xvIG9ic2VydmFjaW9uZXMgbm8gcmVwZXRpZGFzCmBgYAoKClBvZGVtb3MgY29uc3VsdGFyIGxhcyBvcGNpb25lcyBwb3IgZGVmZWN0byBjb21wbGV0YXMgZGUgYGdlb21fcG9pbnQoKWAgW2FxdcOtXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9wb2ludC5odG1sKQoKCgpDYWRhIGBnZW9tX3h4KClgIHRpZW5lIHVuIGRlZmF1bHQgc3RhdGlzdGljLCBwZXJvIHBvZGVtb3MgY2FtYmlhcmxvIHkgZXNwZWNpZmljYXIgb3RyYSBzdGF0IHBhcmEgYWRhcHRhcmxvIGEgbnVlc3RyYXMgbmVjZXNpZGFkZXMuIFBvciBlamVtcGxvLCB0aGUgZGVmYXVsdCBzdGF0aXN0aWMgZm9yIGBnZW9tX2JhcigpYCBpcyBgc3RhdF9iaW4oKWAgcGVybyBwb2RlbW9zIHVzYXIgb3RyYXMgc3RhdF94eC4gT3RyYSB2ZXogcGFyZWNlIHVuIHRyYWJhbGVuZ3VhcywgcGVybyBjdWFuZG8gbG8gZW50aWVuZGVzIGVzIHJlbGF0aXZhbWVudGUgc2VuY2lsbG8uIAoKUG9yIGVqZW1wbG8sIGVuIG51ZXN0cm8gZ3LDoWZpY28gZGUgcHVudG9zLCBwb2RlbW9zIHVzYXIgb3RyYXMgdHJhbnNmb3JtYWNpb25lcyBlc3RhZMOtc3RpY2FzLCB1bmEgZGUgbGFzIHF1ZSBtw6FzIHNlbnRpZG8gdGllbmUgZXMgY2FsY3VsYXIgbWVkaWFzIG3Ds3ZpbGVzIGNvbiB1biBtw6l0b2RvIGRlIGFsaXNhZG8gKHNtb290aGVyKQoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwIDwtIGdncGxvdChpcmlzLCBhZXMoUGV0YWwuTGVuZ3RoLCBTZXBhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llcykpIAoKcCArIGdlb21fcG9pbnQoc3RhdCA9ICJpZGVudGl0eSIpCgpwICsgZ2VvbV9wb2ludChzdGF0ID0gInNtb290aCIsIG1ldGhvZCA9ICJhdXRvIikKYGBgCgoKQXVucXVlIGVuIGVzdGUgY2FzbyBlcyBiYXN0YW50ZSBtw6FzIGbDoWNpbCBoYWNlcmxvIGNvbjoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoKQpwICsgZ2VvbV9wb2ludCgpICsgc3RhdF9zbW9vdGgoKQoKcCArIGdlb21fcG9pbnQoKSArIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIsICAgc2UgPSBGQUxTRSwgIHNpemUgPSAxKQpwICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgICBzZSA9IEZBTFNFLCAgc2l6ZSA9IDEpCgpwICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sID0gIiNDNDIxMjYiLCAgc2UgPSBGQUxTRSwgIHNpemUgPSAxKQpgYGAKCjxicj4KCkFsZ3VuYXMgdHJhbnNmb3JtYWNpb25lcyBlc3RhZMOtc3RpY2FzIMO6dGlsZXMgeSBlbiBxdcOpIGdlb21zIGVzdMOhbiBkaXNwb25pYmxlczoKCiAgICAtIHN0YXRfYmluKCk6IGdlb21fYmFyKCksIGdlb21fZnJlcXBvbHkoKSwgZ2VvbV9oaXN0b2dyYW0oKSAgCiAgICAtIHN0YXRfYmluMmQoKTogZ2VvbV9iaW4yZCgpICAKICAgIC0gc3RhdF9iaW5kb3QoKTogZ2VvbV9kb3RwbG90KCkgIAogICAgLSBzdGF0X2JpbmhleCgpOiBnZW9tX2hleCgpICAgCiAgICAtIHN0YXRfYm94cGxvdCgpOiBnZW9tX2JveHBsb3QoKSAgICAKICAgIC0gc3RhdF9jb250b3VyKCk6IGdlb21fY29udG91cigpICAKICAgIC0gc3RhdF9xdWFudGlsZSgpOiBnZW9tX3F1YW50aWxlKCkgICAKICAgIC0gc3RhdF9zbW9vdGgoKTogZ2VvbV9zbW9vdGgoKSAgCiAgICAtIHN0YXRfc3VtKCk6IGdlb21fY291bnQoKSAgIAoKRXMgcmFybyBxdWUgdGVuZ2Ftb3MgcXVlIHVzYXIgZXN0YXMgZnVuY2lvbmVzIGBzdGF0c194eCgpYCBkaXJlY3RhbWVudGUsIHBlcm8gc2kgcXVpZXJlcyB2ZXIgcXVlIGhhY2VuIGV4YWN0YW1lbnRlIGNvbnZpZW5lIGNvbnN1bHRhciBsYSBkb2N1bWVudGFjacOzbiBwYXJhIHZlciBxdcOpIGhhY2UgeSBjw7NtbyBzZSBhcGxpY2EgZXhhY3RhbWVudGUgY2FkYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIGEgbG9zIGRhdG9zLgoKSGF5IG90cmFzIGZ1bmNpb25lcyBgc3RhdF94eCgpYCBxdWUgbm8gc2UgcHVlZGVuIHV0aWxpemFyIGNvbiBsYXMgZnVuY2lvbmVzIGBnZW9tX3h4KClgOgoKICAgIC0gc3RhdF9lY2RmKCk6IGNvbXB1dGUgYSBlbXBpcmljYWwgY3VtdWxhdGl2ZSBkaXN0cmlidXRpb24gcGxvdC4gIAogICAgLSBzdGF0X2Z1bmN0aW9uKCk6IGNvbXB1dGUgeSB2YWx1ZXMgZnJvbSBhIGZ1bmN0aW9uIG9mIHggdmFsdWVzLiAgCiAgICAtIHN0YXRfc3VtbWFyeSgpOiBzdW1tYXJpc2UgeSB2YWx1ZXMgYXQgZGlzdGluY3QgeCB2YWx1ZXMuICAKICAgIC0gc3RhdF9zdW1tYXJ5MmQoKSwgc3RhdF9zdW1tYXJ5X2hleCgpOiBzdW1tYXJpc2UgYmlubmVkIHZhbHVlcy4gIAogICAgLSBzdGF0X3FxKCk6IHBlcmZvcm0gY2FsY3VsYXRpb25zIGZvciBhIHF1YW50aWxlLXF1YW50aWxlIHBsb3QuICAKICAgIC0gc3RhdF9zcG9rZSgpOiBjb252ZXJ0IGFuZ2xlIGFuZCByYWRpdXMgdG8gcG9zaXRpb24uICAKICAgIC0gc3RhdF91bmlxdWUoKTogcmVtb3ZlIGR1cGxpY2F0ZWQgcm93cy4gIAoKPGJyPgoKIyMjIyBBbGd1bm9zIGVqZW1wbG9zCgpWZWFtb3MgYWxndW5vcyBlamVtcGxvcyDDunRpbGVzOgoKMS4gRW4gdW4gZGlhZ3JhbWEgZGUgY2FqYSBtb3N0cmFyIGxhIG1lZGlhOgogClF1ZXJlbW9zIHF1ZSBsYSBtZWRpYSBzZSByZXByZXNlbnRlIGNvbW8gdW4gcHVudG8sIGVudG9uY2VzIHVzYW1vcyBgZ2VvbV9wb2ludCgpYCBwZXJvIG5vIHF1ZXJlbW9zIHJlcHJlc2VudGFyIGxvcyB2YWxvcmVzIG9yaWdpbmFsZXMsIHNpbm8gbGEgbWVkaWEsIGFzw60gcXVlIGRlbnRybyBkZSBnZW9tIHVzYW1vcyBsYSBvcGNpw7NuIGBzdGF0ID0gc3VtbWFyeWAKIApgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyhTcGVjaWVzLCBTZXBhbC5MZW5ndGgpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZ2VvbV9wb2ludChzdGF0ID0gInN1bW1hcnkiLCBmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSA0KQpgYGAKClNlIGNvbnNlZ3VpcsOtYSBsb3MgbWlzbW8gdXNhbmRvIGRpcmVjdGFtZW50ZSBsYSAgZnVuY2nDs24gYHN0YXRfc3VtbWFyeSgpYCBjb24gbGEgb3BjacOzbiBnZW9tID0gInBvaW50Ii4gU8OtLCBlbiBgZ2dwbG90MmAgbGFzIGNvc2FzIHNlIHB1ZWRlbiBoYWNlciBkZSB2YXJpYXMgbWFuZXJhcyEhCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMsIGFlcyhTcGVjaWVzLCBTZXBhbC5MZW5ndGgpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgc3RhdF9zdW1tYXJ5KGdlb20gPSAicG9pbnQiLCBmdW4ueSA9ICJtZWFuIiwgY29sb3VyID0gInJlZCIsIHNpemUgPSA0KQpgYGAKCjIuICoqT3RybyBlamVtcGxvOioqIGVsIGRlZmF1bHQgc3RhdCBkZSBnZW9tX2hpc3RvZ3JhbSBlcyBzdGF0ID0gImJpbiIsIG1vc3RyYW5kb25vcyBlbCBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgZW4gY2FkYSBiaW4uIFNpIHF1ZXJlbW9zIHF1ZSBub3MgbXVlc3RyZSBmcmVjdWVuY2lhcyByZWxhdMOtdmFzIGFsIGdydXBvIG8gYmluIG3DoXMgbnVtZXJvc286IAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgpKSArIGdlb21faGlzdG9ncmFtKCkgCgpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSBzdGF0KGNvdW50IC8gbWF4KGNvdW50KSkpKQpgYGAKCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI5MCUiLCAgZmlnLmFzcCA9IDUvOX0KcDEgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgpKSArIGdlb21faGlzdG9ncmFtKCkgCnAyIDwtIGdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoKSkgKyBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IHN0YXQoY291bnQgLyBtYXgoY291bnQpKSkpCnAxICsgcDIgICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKCgo8YnI+CgoqKkJvbnVzOioqIENvbiBgc3RhdF9mdW5jdGlvbigpYCBwb2RlbW9zIGRpYnVqYXIgY3VydmFzIGRlIGRlbnNpZGFkOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGYgPC0gdGliYmxlKHggPSBjKC0yMCwgMjApKQogIApnZ3Bsb3QoZGYsIGFlcyh4ID0geCkpICsKc3RhdF9mdW5jdGlvbihmdW4gPSBkbm9ybSwgYXJncyA9IGxpc3QobWVhbiA9IDAsIHNkID0gNSksIGNvbG9yID0gImJsYWNrIikgKwpzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBhcmdzID0gbGlzdChtZWFuID0gMCwgc2QgPSAxKSwgY29sb3IgPSAicmVkIikgKwpzdGF0X2Z1bmN0aW9uKGZ1biA9IGRub3JtLCBhcmdzID0gbGlzdChtZWFuID0gMCwgc2QgPSAzKSwgY29sb3IgPSAiYmx1ZSIpCmBgYAoKCjxicj4KCiMjIyAzLjggUG9zaXRpb24gYWRqdXN0bWVudHMKCgo+IEFsbCBsYXllcnMgaGF2ZSBhIHBvc2l0aW9uIGFkanVzdG1lbnQgdGhhdCByZXNvbHZlcyBvdmVybGFwcGluZyBnZW9tcy4gT3ZlcnJpZGUgdGhlIGRlZmF1bHQgYnkgdXNpbmcgdGhlIHBvc2l0aW9uIGFyZ3VtZW50IHRvIHRoZSBnZW9tXyBvciBzdGF0XyBmdW5jdGlvbi4KCgpMb3MgYWp1c3RlcyBkZSBwb3NpY2nDs24gYWZlY3RhbiBhIGxhIHBvc2ljacOzbiBkZSBsb3MgZWxlbWVudG9zIGRlIHVuYSBjYXBhLiBMb3MgZ3LDoWZpY29zIGVuIGxvcyBxdWUgbcOhcyBzZSB1dGlsaXphbiBsb3MgYWp1c3RlcyBkZSBwb3NpY2nDs24gc29uIGxvcyBncsOhZmljb3MgZGUgYmFycmFzLiBTdSBwb3NpY2nDs24gcG9yIGRlZmVjdG8gZXMgcG9zaXRpb24gPSAic3RhY2siLiBTZSBwdWVkZW4gY2FtYmlhciBjb24gZWwgYXJndW1lbnRvIGdlb21fYmFyKHBvc2l0aW9uID0gInh4eHgiKWAgLCBhdW5xdWUgc2kgdXNhcyBsYXMgZnVuY2lvbmVzIGBwb3NpdGlvbl94eCgpYCB0aWVuZXMgbcOhcyBmbGV4aWJpbGlkYWQ6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcyAsIGFlcyhTcGVjaWVzKSkgKyBnZW9tX2JhcigpCmdncGxvdChtdGNhcnMsIGFlcyhjeWwpKSArICBnZW9tX2JhcigpCmBgYAoKCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI5MCUiLCAgZmlnLmFzcCA9IDUvOX0KcDEgPC0gZ2dwbG90KGlyaXMgLCBhZXMoU3BlY2llcykpICsgZ2VvbV9iYXIoKSAgIy0gdGVtYSBwb3IgZGVmZWN0bwpwMiA8LSBnZ3Bsb3QobXRjYXJzLCBhZXMoY3lsKSkgKyAgZ2VvbV9iYXIoKQoKcDEgKyBwMiAgKyBwbG90X2xheW91dChuY29sID0gMikKYGBgCgpQYXJhIHBvZGVyIHZpc3VhbGl6YXIgZ3LDoWZpY29zIGRlIGJhcnJhcyBjb24gMiB2YXJpYWJsZXMsdGVuZW1vcyBxdWUgdXNhciBvdHJvIGRhdGFzZXQ6IGBtdGNhcnNgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2JhcigpICMtIHBvcwpnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikKZ2dwbG90KG10Y2FycywgYWVzKGZhY3RvcihjeWwpLCBmaWxsID0gZmFjdG9yKHZzKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKQpnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlMihwcmVzZXJ2ZSA9ICJzaW5nbGUiKSkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjEwMCUiLCAgZmlnLmFzcCA9IDUvOX0KcDEgPC0gZ2dwbG90KG10Y2FycywgYWVzKGZhY3RvcihjeWwpLCBmaWxsID0gZmFjdG9yKHZzKSkpICsgZ2VvbV9iYXIoKSAjLSBwb3MKcDIgPC0gZ2dwbG90KG10Y2FycywgYWVzKGZhY3RvcihjeWwpLCBmaWxsID0gZmFjdG9yKHZzKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpCnAzIDwtIGdncGxvdChtdGNhcnMsIGFlcyhmYWN0b3IoY3lsKSwgZmlsbCA9IGZhY3Rvcih2cykpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikKcDQgPC0gZ2dwbG90KG10Y2FycywgYWVzKGZhY3RvcihjeWwpLCBmaWxsID0gZmFjdG9yKHZzKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZShwcmVzZXJ2ZSA9ICJzaW5nbGUiKSkKCnAxICsgcDIgKyBwMyArIHA0ICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKPGJyPgoKUG9yIGVqZW1wbG8sIHBvZGVtb3MgbW9kaWZpY2FyIGxhIHBvc2ljacOzbiBwb3IgZGVmZWN0byBkZSBudWVzdHJvIGdyw6FmaWNvIGRlIHB1bnRvcyBjb24gZWwgaXJpcyBkYXRhc2V0IHVzYW5kbyBkb3MgZW5mb3F1ZXM6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3BvaW50KHBvc2l0aW9uID0gImppdHRlciIsIGNvbG9yID0gInBpbmsiKSAKZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9qaXR0ZXIoIGNvbG9yID0gInBpbmsiKSAKYGBgCgoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSArIGdlb21fcG9pbnQocG9zaXRpb24gPSAiaml0dGVyIiwgY29sb3IgPSAicGluayIpIApgYGAKCmBnZW9tX2ppdHRlcigpYCwgbyBhbHRlcm5hdGl2YW1lbnRlIGBnZW9tX3BvaW50KHBvc2l0aW9uID0gImppdHRlciIpYCBjYW1iaWEgbGEgcG9zaWNpw7NuIG9yaWdpbmFsIGRlIGxvcyBkYXRvcyBhw7FhZGllbmRvIHVuIHBvY28gZGUgcnVpZG8sIGhhY2llbmRvIHF1ZSBzZSBkZXNwbGFjZW4gdW4gcG9jby4gRXN0YSB0w6ljbmljYSBzZSB1c2EgbXVjaG9zIGN1YW5kbyBoYXkgbXVjaG9zIGRhdG9zIHNpbWlsYXJlcyAob3ZlcnBsb3R0aW5nKS4KCgo8YnI+CgojIyMgMy45IENvb3JkZW5hZGFzCgo+IFRoZSBjb29yZGluYXRlIHN5c3RlbSBkZXRlcm1pbmVzIGhvdyB0aGUgeCBhbmQgeSBhZXN0aGV0aWNzIGNvbWJpbmUgdG8gcG9zaXRpb24gZWxlbWVudHMgaW4gdGhlIHBsb3QuIFRoZSBkZWZhdWx0IGNvb3JkaW5hdGUgc3lzdGVtIGlzIENhcnRlc2lhbiAoY29vcmRfY2FydGVzaWFuKCkpLCB3aGljaCBjYW4gYmUgdHdlYWtlZCB3aXRoIGNvb3JkX21hcCgpLCBjb29yZF9maXhlZCgpLCBjb29yZF9mbGlwKCksIGFuZCBjb29yZF90cmFucygpLCBvciBjb21wbGV0ZWx5IHJlcGxhY2VkIHdpdGggY29vcmRfcG9sYXIoKQoKClBvciBlamVtcGxvOiBgY29vcmRfZml4ZWQoKWAuIEVuIG51ZXN0cm8gZ3LDoWZpY28sIHRhbnRvIGxhIGxvbmdpdHVkIGRlbCBww6l0YWxvIGNvbW8gZGVsIHPDqXBhbG8gc2UgbWlkZW4gZW4gbGFzIG1pc21hcyB1bmlkYWRlcywgYXPDrSBxdWUgc3UgcmF0aW8gaW1wbMOtY2l0byBlcyAxIGEgMS4gQ2FtYmllbW9zIGVsIHJhdGlvIGRlIGxhcyBjb29yZGVuYWRhcyBjb24gYGNvb3JkX2ZpeGVkKClgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwIDwtIGdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArIGdlb21fcG9pbnQoKSAKCnAgKyBjb29yZF9maXhlZChyYXRpbyA9IDEvMykKcCArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMy8xKQpgYGAKCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjEwMCUiLCAgZmlnLmFzcCA9IDUvOX0KcDEgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpIApwMiA8LSBwMSArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMS8zKQpwMyA8LSBwMSArIGNvb3JkX2ZpeGVkKHJhdGlvID0gMy8xKQoKcDIgKyBwMyArIHBsb3RfbGF5b3V0KG5yb3cgPSAxKQojaHR0cHM6Ly93d3cuc3RhdHdvcnguY29tL2RlL2Jsb2cvY29vcmRpbmF0ZS1zeXN0ZW1zLWluLWdncGxvdDItZWFzaWx5LW92ZXJsb29rZWQtYW5kLXJhdGhlci11bmRlcnJhdGVkLwpgYGAKCgoKCjxicj4KCgojIyA0LiBDb21iaW5hbmRvIGdyw6FmaWNvcwoKCkxhIHTDqWNuaWNhIGRlbCBmYWNldGluZyBlcyB1bmEgZmFudMOhc3RpY2EgaGVycmFtaWVudGEgcGFyYSBkaXZpZGlyIHVuIGdyw6FmaWNvIGVuIHZhcmlvcywgZW4gcHJpbmNpcGlvIGVuIGZ1bmNpw7NuIGRlIHVuYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSwgc2kgcXVlcmVtb3MgdXNhciB1bmEgdmFyaWFibGUgY29udGludWEgcHJpbWVybyBoYWJyw61hIHF1ZSBkaXNjcmV0aXphcmxhLiAqKlBFUk8qKiwgYSB2ZWNlcyBsbyBxdWUgaW50ZXJlc2EgZXMgY3JlYXIgdW5hIGZpZ3VyYSBjb21wdWVzdGEgZGUgdmFyaW9zIGdyw6FmaWNvcyBkaWZlcmVudGVzLiBFc3RvIG5vIGVzIHVuIGVsZW1lbnRvIG3DoXMgZGUgZ2dwbG90MiBlcyB1bmEgb3BlcmFjacOzbiBxdWUgaGFjZW1vcyBzb2JyZSB1biBncnVwbyBkZSB2YXJpb3MgZ3LDoWZpY29zLgoKClBvZGVtb3MgaGFjZXJsbyBjb24gdmFyaW9zIHBhcXVldGVzOgoKLSBjb24gZWwgcGFxdWV0ZSBgZ3JpZEV4dHJhYCB5IHN1IGZ1bmNpw7NuIGBncmlkLmFycmFuZ2UoKWAKCmBgYHtyLCBldmFsID0gVFJVRSwgb3V0LndpZHRoID0gIjgwJSJ9CmxpYnJhcnkoZ3JpZEV4dHJhKQpwMSA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoLCBjb2xvciA9IFNwZWNpZXMpKSArIGdlb21fcG9pbnQoKQpwMiA8LSBnZ3Bsb3QoaXJpcykrIGFlcyhTcGVjaWVzLCBTZXBhbC5MZW5ndGgpICsgZ2VvbV9ib3hwbG90KCkKCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2wgPSAyLCB3aWR0aHMgPSBjKDYuNSwgMy41KSkKYGBgCgpBZGVtw6FzIGRlIGxvcyBhcmd1bWVudG9zIG5jb2wsIG5yb3cgeSB3aWR0aHMsIGNvbiAgYGdyaWRFeHRyYWAgc2UgcHVlZGVuIGhhY2VyIGNvbXBvc2ljaW9uZXMgW23DoXMgY29tcGxlamFzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ3JpZEV4dHJhL3ZpZ25ldHRlcy9hcnJhbmdlR3JvYi5odG1sKQoKCi0gY29uIGVsIHBhcXVldGUgW2BwYXRjaHdvcmtgXShodHRwczovL2dpdGh1Yi5jb20vdGhvbWFzcDg1L3BhdGNod29yaykgCgoKCmBgYHtyLCBldmFsID0gVFJVRSwgb3V0LndpZHRoID0gIjgwJSJ9CmxpYnJhcnkocGF0Y2h3b3JrKQpwMSArIHAyICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKLSBjb24gZWwgcGFxdWV0ZSBbYGNvd3Bsb3RgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvY293cGxvdC92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpLiBFc3RlIHBhcXVldGUgdGFtYmnDqW4gc2UgcHVlZGUgdXRpbGl6YXIgcGFyYSBoYWNlciBhbm90YWNpb25lcyBvIHBhcmEgaW5jb3Jwb3JhciBpbcOhZ2VuZXMgYSBudWVzdHJvcyBncsOhZmljb3MKCgpgYGB7cn0KbGlicmFyeShjb3dwbG90KQpnZ2RyYXcocDEpICsgZHJhd19pbWFnZShoZXJlOjpoZXJlKCIuL2ltYWdlbmVzL0NhcHR1cmEuSlBHIiksIAogICAgICAgICAgICAgICB4ID0gMSwgeSA9IDEsIGhqdXN0ID0gMSwgdmp1c3QgPSAxLCB3aWR0aCA9IDAuMzMsIGhlaWdodCA9IDAuNDIpCmBgYAoKCjxicj4KCgojIyA1LiBFeHBvcnRhbmRvIGdyw6FmaWNvcwoKTGEgbWF5b3LDrWEgZGUgbGFzIHZlY2VzLCBjdWFuZG8gY3JlYW1vcyB1biBncsOhZmljbyBsbyB2ZW1vcyBpbm1lZGlhdGFtZW50ZSwgcGVybyB0YW1iacOpbiBwb2RlbW9zIGFzaWduYXJsZSB1biBub21icmUgeSBtYW5pcHVsYXJsbyBtw6FzIGFkZWxhbnRlLiAKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgZ2VvbV9wb2ludCgpCnAgKyBnZW9tX2xpbmUoKQpgYGAKCgpVbmEgdmV6IHF1ZSB0aWVuZXMgZWwgZ3LDoWZpY28gZ3VhcmRhZG8gY29tbyB1biBvYmpldG8gZW4gZWwgUiBlbnZpcm9ubWVudCBwdWVkZXMgaGFjZXIgdmFyaWFzIGNvc2FzIGNvbiDDqWw6CgoxLiB2ZXJsbyBlbiBsYSBwYW50YWxsYSBjb24gYHByaW50KClgIG8gbGxhbcOhbmRvbG8gY29uICJzdSBub21icmUiLgoKMi4gZ3VhcmRhcmxvIGVuIHVuIGZpY2hlcm8gZW4gZGlmZXJlbnRlcyBmb3JtYXRvcy4gUGllbnNhIHF1ZSBlc3RhbW9zIGd1YXJkYW5kbyBlbCBncsOhZmljbywgbGEgaW1hZ2VuLCBsYSByZXByZXNlbnRhY2nDs24gZGVsIGdyw6FmaWNvLCBubyBlbCBvYmpldG8gUi4KCiAgICAtIFBvZGVtb3MgdXNhciBsYSAiRXhwb3J0IFRhYiIgZW4gZWwgUGxvdCBQYW5lIGRlIFJzdHVkaW8uIExvIGdyYWJhcsOhIGVuIGJhamEgcmVzb2x1Y2nDs24uICBUYW1iacOpbiBwb2RlbW9zIGNhbWJpYXIgbGFzIGRpbWVuc2lvbmVzIChhbmNodXJhIHkgYWx0dXJhKSBkZWwgZ3LDoWZpY28uIFNpIHZhbW9zIGEgdXRpbGl6YXIgZWwgZ3LDoWZpY28gZW4gV29yZCBlcyBjb252ZW5pZW50ZSBndWFyZGFyIGVsIGdyw6FmaWNvIGNvbW8gTWV0YWZpbGUuCiAgICAKICAgIC0gQ29tbyBhbHRlcm5hdGl2YSBwb2RlbW9zIHVzYXIgbGEgZnVuY2nDs24gYGdnc2F2ZSgpYCBxdWUsIGFkZW1hcywgbm9zIHBlcm1pdGUgY2FtYmlhciBlbCB0YW1hw7FvIHkgbGEgcmVzb2x1Y2nDs24gZGVsIGdyw6FmaWNvIGNvbiBsb3MgYXJndW1lbnRvcyB3aWR0aCwgaGVpZ2h0IHkgZHBpLiBOb3RhOiBMb3MgcGFyw6FtZXRyb3Mgd2lkdGggYW5kIGhlaWdodCB0YW1iacOpbiBkZXRlcm1pbmFuIGVsIHRhbWHDsW8gZGUgbGEgZnVlbnRlIGRlbCBncsOhZmljbyBndWFyZGFkby4KCiAgICAKCmBgYHtyLCBldmFsID0gRkFMU0V9Cmdnc2F2ZSgiLi9ncmFmX291dC9teV9ncmFmaWNvX2NodWxvLnBuZyIsIHAsIHdpZHRoID0gMTUsIGhlaWdodCA9IDEwKQpnZ3NhdmUoImZpbGVuYW1lLnBuZyIsIHBsb3QgPSBteV9wbG90LCB3aWR0aCA9IDgsIGhlaWdodCA9IDYsIHVuaXRzID0gImluIiwgZHBpID0gInJldGluYSIpCgojIFRiIGZ1bmNpb25hIHBhcmEgZmlndXJhcyBjb21wdWVzdGFzIGRlIHZhcmlvcyBncsOhZmljb3MKZ3JhZmljb19jb21iaW5hZG8gPC0gZ3JpZC5hcnJhbmdlKHAxLCBwMiwgbmNvbCA9IDIsIHdpZHRocyA9IGMoNiwgNCkpCmdnc2F2ZSgiZmlnX291dHB1dC9teV9jb21ib19wbG90LnBuZyIsIGdyYWZpY29fY29tYmluYWRvLCB3aWR0aCA9IDEwLCBkcGkgPSAzMDApCmBgYAoKCi0gRmluYWxtZW50ZSwgcG9kZW1vcyBndWFyZGFyIHVuYSBjb3BpYSBjb21wbGV0YSBkZWwgZ3LDoWZpY28sIG5vIGRlIHN1IHJlcHJlc2VudGFjacOzbiB2aXN1YWwsIHNpbm8gZGVsIG9iamV0byBSIGNvbiBgc2F2ZVJEUygpYCwgcGFyYSBsdWVnbyBsZWVybG8gY29uIGByZWFkUkRTKClgLiBBdW5xdWUsIHlhIHF1ZSBub3MgcG9uZW1vcywgZXMgbWVqb3IgZ3VhcmRhciBlbCBzY3JpcHQgcXVlIGdlbmVyYSBlbCBvYmpldG8uIFPDs2xvIHRlbmRyw61hIHNlbnRpZG8gc2kgIGZ1ZXNlIG11eSBjb3N0b3NvLCBwb3IgZWplbXBsbyBlbiB0w6lybWlub3MgZGUgdGllbXBvLCByZXByb2R1Y2lyIGVsIGdyw6FmaWNvLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0Kc2F2ZVJEUyhwLCAicGxvdC5yZHMiKQpteV92YWxpb3NvX2dyYWZpY28gPC0gcmVhZFJEUygicGxvdC5yZHMiKQpgYGAKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgNi4gVGlwb3MgZGUgZ3LDoWZpY29zCgpFbiBlc3TDoSBzZWNjacOzbiBwcmVzZW50YXJlbW9zIGFsZ3Vub3MgZWplbXBsb3MgZGUgYWxndW5vcyBkZSBsb3MgZ3LDoWZpY29zIG3DoXMgdXRpbGl6YWRvcyBlbiBlbCBhbsOhbGlzaXMgZGUgZGF0b3MuIFB1ZWRlcyB2ZXIgbGlzdGFkb3MgbcOhcyBjb21wbGV0b3MgZW46CgogIC0gW1ItZ3JhcGggZ2FsbGVyeV0oaHR0cHM6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8pOiBJbXByZXNpb25hbnRlISEKCgogIC0gW1RvcCA1MCBnZ3Bsb3QyIFZpc3VhbGl6YXRpb25zIChXaXRoIEZ1bGwgUiBDb2RlKSwgZGUgci1zdGF0aXN0aWNzLmNvXShodHRwOi8vci1zdGF0aXN0aWNzLmNvL1RvcDUwLUdncGxvdDItVmlzdWFsaXphdGlvbnMtTWFzdGVyTGlzdC1SLUNvZGUuaHRtbCk6IG3DoXMgYsOhc2ljbyBwZXJvIG11eSBkaWTDoWN0aWNvLgoKICAKICAtIFtFeHRlbnNpb25lcyBnZ3Bsb3QyXShodHRwOi8vd3d3LmdncGxvdDItZXh0cy5vcmcvZ2FsbGVyeS8pOiBJbXByZXNpb25hbnRlISEKCiAgLSBbUi1ncmFwaCBnYWxsZXJ5IC0gZ2dwbG90MiBwYWNrYWdlXShodHRwOi8vci1ncmFwaC1nYWxsZXJ5LmNvbS9nZ3Bsb3QyLXBhY2thZ2UuaHRtbCk6IG90cmEgdmV6LCBpbXByZXNpb25hbnRlISEKCgo8YnI+CgojIyMgNi4xIEhpc3RvZ3JhbWFzCgpTZSB1dGlsaXphbiBwYXJhIG1vc3RyYXIgbGEgKipkaXN0cmlidWNpw7NuIGRlIFVOQSB2YXJpYWJsZSBjb250aW51YSoqLCBwb3IgZWplbXBsbyBsYSBsb25naXR1ZCBkZWwgc8OpcGFsbyAoU2VwYWwuTGVuZ3RoKS4KClBhcmEgaGFjZXIgaGlzdG9ncmFtYXMgZW4gZ2dwbG90MiBzZSB1dGlsaXphIGBnZW9tX2hpc3RvZ3JhbWAgeSwgYSB2ZWNlcywgYGdlb21fZnJlcXBvbHkoKWAuIExvcyBkb3MgZ2VvbXMgdHJhYmFqYW4gZGUgbGEgbWlzbWEgbWFuZXJhLCBkaXZpZGVuIGxhIHZhcmlhYmxlIHggZW4gaW50ZXJ2YWxvcyB5IGN1ZW50YW4gbGFzIG9ic2VydmFjaW9uZXMgZW4gY2FkYSBpbnRlcnZhbG8sIHBhcmEgbW9zdHJhcmxhcyBlbiBlbCBlamUgWS4gTGEgZGlmZXJlbmNpYSBlbnRyZSBlbGxvcyBlcyBxdWUgYGdlb21faGlzdG9ncmFtYCB1dGlsaXphIGJhcnJhcyBwYXJhIG1vc3RyYXIgZWwgbsO6bWVybyBkZSBvYnNlcnZhY2lvbmVzIG8gZnJlY3VlbmNpYSBhYnNvbHV0YSwgbWllbnRyYXMgcXVlICBgZ2VvbV9mcmVxcG9seSgpYCB1c2EgbGluZWFzLiAKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpICsgZ2VvbV9oaXN0b2dyYW0oKSAKYGBgCgpDb21vIHZlcywgbG8gcXVlIGhhY2UgdW4gaGlzdG9ncmFtYSBlcyBkaXZpZGlyIGVsIGVqZSBYIGVuIGludGVydmFsb3MgbyAiYmlucyIgeSBtb3N0cmFyIGVuIGVsIGVqZSBZIGVsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyBkZSB4IHF1ZSBjYWVuIGVuIGNhZGEgImJpbiIuIEFkZW3DoXMsIGdncGxvdDIgbm9zIGF2aXNhIGRlIHF1ZSBwb3IgZGVmZWN0byBzZSBkaXZpZGUgZWwgZWplIFggZW4gMzAgaW50ZXJ2YWxvcywgcGVybyBxdWUgcG9kZW1vcyBjYW1iaWFybG8gY29uIGBiaW5zID0gbXlfbl9kZV9iaW5zYC4gCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpCnAgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNDApICsgeGxhYigiNDAgaW50ZXJ2YWxvcyIpCnAgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNCkgKyB4bGFiKCJTw7NsbyA0IGludGVydmFsb3MiKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiOTUlIn0KcCA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpCnAxIDwtIHAgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNDApICsgeGxhYigiNDAgaW50ZXJ2YWxvcyIpCnAyIDwtIHAgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNCkgKyB4bGFiKCJTw7NsbyA0IGludGVydmFsb3MiKQpwMSArIHAyICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKCk90cmEgb3BjacOzbiBpbnRlcmVzYW50ZSBlcyBlbGVnaXIgbGEgYW5jaHVyYSBkZWwgaW50ZXJ2YWxvIGNvbiBsYSBvcGNpw7NuIGBiaW53aWR0aGAuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwIDwtIGdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoKSkKcCArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4xKSArIHhsYWIoIkhlIGVsZWdpZG8gbGEgYW5jaHVyYSA9IDAuMSIpCnAgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEuMSkgKyB4bGFiKCJFc3RhIHZleiBsYSBhbmNodXJhID0gMS4xIikKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjk1JSJ9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgpKQpwMSA8LSBwICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEpICsgeGxhYigiSGUgZWxlZ2lkbyBsYSBhbmNodXJhID0gMC4xIikKcDIgPC0gcCArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMS4xKSArIHhsYWIoIkVzdGEgdmV6IGxhIGFuY2h1cmEgPSAxLjEiKQpwMSArIHAyICsgcGxvdF9sYXlvdXQobmNvbCA9IDIpCmBgYAoKClNpIGVuIGVsIGVqZSBZIHF1ZXJlbW9zIGZyZWN1ZW5jaWFzIHJlbGF0aXZhcyBvIHBvcmNlbnRhamVzIGVuIGx1Z2FyIGRlIGNvdW50cywgcG9kZW1vcyBoYWNlcmxvIGNvbjoKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSBzdGF0KGNvdW50KSAvIHN1bShjb3VudCkpLCBiaW5zID0gMTApICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KQpgYGAKCgpTZSBwdWVkZSBtZWpvcmFyIGJhc3RhbnRlIGxhIGFwYXJpZW5jaWEgZGVsIGhpc3RvZ3JhbWEganVnYW5kbyBjb24gbG9zIGNvbG9yZXMgeSBvcGNpb25lczoKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpICsgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwLCBjb2xvciA9ICJibGFjayIsIGZpbGwgPSAidG9tYXRvIikKYGBgCgoKClNpIGFzb2NpYW1vcy9tYXBlYW1vcyBsYSB2YXJpYWJsZSBTcGVjaWVzIGEgYWxndW5hIGVzdMOpdGljYSwgcG9yIGVqZW1wbG8gYSBsYSBlc3TDqXRpY2EgImZpbGwiIG8gcmVsbGVubyBkZSBsYXMgYmFycmFzIGNvbiBgYWVzKGZpbGwgPSBTcGVjaWVzKWAsIHNlIHZpc3VhbGl6YXLDoSBxdcOpIHBhcnRlIGRlIGNhZGEgaW50ZXJ2YWxvIHBlcnRlbmVjZSBhIGNhZGEgY2xhc2UgZGUgbGlyaW8uCgoKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCkpICsgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwLCBhZXMoZmlsbCA9IFNwZWNpZXMpLCBjb2xvciA9ICJibGFjayIpCmBgYAoKCkV2aWRlbnRlbWVudGUsIHRhbWJpw6luIHBvZGVtb3MgdmlzdWFsaXphciBjb21vIGNhbWJpYSBsYSBkaXN0cmlidWNpw7NuIGRlIGxhIGFuY2h1cmEgZGVsIHPDqXBhbG8gZW50cmUgbG9zIDMgdGlwb3MgZGUgbGlyaW9zIHNpIGhhY2Vtb3MgdW4gc21hbGwgbXVsdGlwbGU6CgoKYGBge3J9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIGZpbGwgPSBTcGVjaWVzKSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAsIGNvbG9yID0gImJsYWNrIikKcCArIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoU3BlY2llcykpCmBgYAoKUGFyYSBtZWpvcmFyIGxhIHZpc3VhbGl6YWNpw7NuIHBvZGVtb3MgcG9uZXIgZW4gZWwgZm9uZG8gZWwgaGlzdG9ncmFtYSBwYXJhIHRvZG9zIGxvcyBkYXRvcy4gTG8gYXByZW5kw60gW2FxdcOtXShodHRwczovL2Ryc2ltb25qLnN2YnRsZS5jb20vcGxvdHRpbmctYmFja2dyb3VuZC1kYXRhLWZvci1ncm91cHMtd2l0aC1nZ3Bsb3QyKSwgYXVucXVlIGFob3JhIG1pc21vIGhheSB1biBwYXF1ZXRlIHBhcmEgaW1wbGVtZW50YXIgZXN0YSB0w6ljbmljYSwgZXMgZWwgcGFxdWV0ZSBbYGdnaGlnaGxpZ2h0YF0oaHR0cHM6Ly95dXRhbm5paGlsYXRpb24uZ2l0aHViLmlvL2dnaGlnaGxpZ2h0L2luZGV4Lmh0bWwpLgoKCmBgYHtyfQppcmlzX2JhY2tncm91bmcgPC0gaXJpcyAlPiUgc2VsZWN0KC1TcGVjaWVzKQpnZ3Bsb3QoaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgpKSArCiAgZ2VvbV9oaXN0b2dyYW0oZGF0YSA9IGlyaXNfYmFja2dyb3VuZywgZmlsbCA9ICJncmV5IiwgYmlucyA9IDE1KSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGwgPSBTcGVjaWVzKSwgYmlucyA9IDE1KSArCiAgZmFjZXRfZ3JpZChjb2xzID0gdmFycyhTcGVjaWVzKSkKYGBgCgo8YnI+CgojIyMjIGdlb21fZGVuc2l0eSgpCgpVbmEgYWx0ZXJuYXRpdmEgYSBsb3MgaGlzdG9ncmFtYXMgc29uIGxvcyBncsOhZmljb3MgZGUgZGVuc2lkYWQgY29uIGBnZW9tX2RlbnNpdHkoKWAuIFBlcm8sIHNlZ8O6biBIYWRsZXk6IAoKPnRoZXkgYXJlIGhhcmRlciB0byBpbnRlcnByZXQgc2luY2UgdGhlIHVuZGVybHlpbmcgY29tcHV0YXRpb25zIGFyZSBtb3JlIGNvbXBsZXguIFRoZXkgYWxzbyBtYWtlIGFzc3VtcHRpb25zIHRoYXQgYXJlIG5vdCB0cnVlIGZvciBhbGwgZGF0YSwgbmFtZWx5IHRoYXQgdGhlIHVuZGVybHlpbmcgZGlzdHJpYnV0aW9uIGlzIGNvbnRpbnVvdXMsIHVuYm91bmRlZCwgYW5kIHNtb290aC4KCkNvbW8gZGljZSBIYWRsZXksIGBnZW9tX2RlbnNpdHkoKWAgZXN0aW1hIGxhIGZ1bmNpw7NuIGRlIGRlbnNpZGFkLCBwb3IgbG8gcXVlIGxhIGVzdGltYWNpw7NuIGRlcGVuZGUgZGUgdW5hIHNlcmllIGRlIHBhcmFtw6l0cm9zIGNvbW8gYGFkanVzdGAuIEhhY2Vtb3MgdXNvIGRlIGB4bGltKDMsIDkpYCBwYXJhIGV4cGFuZGlyIGxvc2zDrW1pdGVzIGRlbCBlamUgWC4KCgpgYGB7ciwgb3V0LndpZHRoID0gIjg1JSJ9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoKSkgKyAKICBnZW9tX2RlbnNpdHkoY29sb3IgPSAicmVkIiwgICBzaXplID0gMS4yKSArICAKICBnZW9tX2RlbnNpdHkoY29sb3IgPSAiYmx1ZSIsICBzaXplID0gMS4yLCBhZGp1c3QgPSAzKSArCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDEuMiwgYWRqdXN0ID0gMC41KSArICB4bGltKDIsIDEwKQpgYGAKCjxicj4KClBvZGVtb3MgYXNvY2lhciBsYSB2YXJpYWJsZSBTcGVjaWVzIGEgbGEgZXN0w6l0aWNhIGZpbGwgY29uIGBhZXMoZmlsbCA9IFNwZWNpZXMpYCBwYXJhIG9idGVuZXIgdW5hIGVzdGltYWNpw7NuIGRlIGxhIGRlbnNpZGFkIHBhcmEgY2FkYSB0aXBvIGRlIGxpcmlvLgoKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgZmlsbCA9IFNwZWNpZXMpKSArIGdlb21fZGVuc2l0eShwb3NpdGlvbiA9ICJzdGFjayIsIGFscGhhID0gMC41KQpgYGAKCjxicj4KCkhheSBtdWNoYXMgb3RyYXMgcG9zaWJpbGlkYWRlcy4gUG9yIGVqZW1wbG86CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgZmlsbCA9IFNwZWNpZXMpKSArIGdlb21fZGVuc2l0eShwb3NpdGlvbiA9ICJmaWxsIiwgYWxwaGEgPSAwLjUpICMtIHBvc2l0aW9uID0gImZpbGwiCgpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgc3RhdChjb3VudCksIGZpbGwgPSBTcGVjaWVzKSkgKyBnZW9tX2RlbnNpdHkocG9zaXRpb24gPSAic3RhY2siLCBhbHBoYSA9IDAuNSkgIy0gc3RhdChjb3VudCkKYGBgCgoKTXVjaGFzIHZlY2VzIHNlIHN1ZWxlbiBoYWNlciBsb3MgaGlzdG9ncmFtYXMgc3VwZXJwb25pZW5kbyBsYSBmLiBkZSBkZW5zaWRhZCBub3JtYWwgbyB1bmEgZXN0aW1hY2nDs24gZGUgbGEgZGVuc2lkYWQgZGUgeCBjb24gYGdlb21fZGVuc2l0eSgpYCBvIGNvbiBgZ2VvbV9saW5lKHN0YXQ9ImRlbnNpdHkiKWAuIFBhcmEgcXVlIGVsIGdyw6FmaWNvIHNlIHZlYSBtZWpvciwgYWp1c3RhcmVtb3MgZWwgZWplIFggY29uIGAgeGxpbSgpYDoKCgpgYGB7cn0KIy0gY2FsY3VsYW1vcyBtZWRpYSB5IGRlc3ZpYWNpw7NuIHRpcGljYSBkZSBTZXBhbC5MZW5ndGggcGFyYSBsdWVnbyB1c2FybGFzIHBhcmEgY29uc3RydWlyIGxhIGN1cnZhIG5vcm1hbAptZWRpYSA8LSBtZWFuKGlyaXMkU2VwYWwuTGVuZ3RoLCBuYS5ybSA9IFRSVUUpICAgICAjLSBtZWRpYSBkZSBsYSBsb25naXR1ZCBkZWwgc8OpcGFsbwpkZXN2aWFjaW9uIDwtIHNkKGlyaXMkU2VwYWwuTGVuZ3RoLCBuYS5ybSA9IFRSVUUpICAjLSBkZXN2aWFjacOzbiAKCiMtIGhhY2Vtb3MgZWwgaGlzdG9ncmFtYQpwIDwtIGdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoKSkgKwogICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeT0uLmRlbnNpdHkuLiksICBjb2xvcj0iYmxhY2siLCBmaWxsID0gInN0ZWVsYmx1ZSIsIGFscGhhID0gMC4yKQoKIy0gbGUgYcOxYWRpbW9zIGxhIGRlbnNpaWRhZCBlc3RpbWFkYSB5IGxhIG5vcm1hbApwICsgZ2VvbV9kZW5zaXR5KCBjb2xvcj0icHVycGxlIiwgc2l6ZSA9IDEpICsKICAgIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMSwgYXJncyA9IGxpc3QobWVhbiA9IG1lZGlhLCBzZCA9IGRlc3ZpYWNpb24pKSAgKyAKICAgIHhsaW0oYyhtaW4oaXJpcyRTZXBhbC5MZW5ndGgpLTEsIDkpKQoKYGBgCgoKU2kgbmVjZXNpdGFzIHNhYmVyIG3DoXMgY29zYXMgc29icmUgbG9zIGhpc3RvZ3JhbWFzIHB1ZWRlcyBhY3VkaXIgW2FxdcOtXShodHRwczovL2VkYXYuaW5mby9oaXN0by5odG1sKSBvIFthcXXDrV0oaHR0cDovL3QtcmVkYWN0eWwuaW8vYmxvZy8yMDE2LzAyL2NyZWF0aW5nLXBsb3RzLWluLXItdXNpbmctZ2dwbG90Mi1wYXJ0LTctaGlzdG9ncmFtcy5odG1sKS4KCgoKIyMjIyBKb3kgRGl2aXNpb24gcGxvdHMKCkhhY2UgcG9jbyBhcGFyZWNpw7MgZWwgcGFxdWV0ZSBbZ2dyaWRnZXNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3JpZGdlcy92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpIHkgc2UgcHVzaWVyb24gZGUgbW9kYSBlc3RvcyB0aXBvcyBkZSBncsOhZmljb3M6CgoKCmBgYHtyfQpsaWJyYXJ5KGdncmlkZ2VzKQpnZ3Bsb3QoaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTcGVjaWVzKSkgKyBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gU3BlY2llcyksIGFscGhhID0gMC41KQpgYGAKCkNvbW8gc2UgcGFyZWNlbiBhIGxhIFttw610aWNhIHBvcnRhZGFdKGh0dHBzOi8vY29kaWdvZXNwYWd1ZXRpLmNvbS9ub3RpY2lhcy9jaWVuY2lhL2hpc3RvcmlhLXNlY3JldGEtdW5rbm93bi1wbGVhc3VyZXMvKSBkZWwgcHJpbWVyIGRpc2NvIGRlIEpveSBEaXZpc2lvbiwgYWxndW5vcyBsb3MgY29ub2NlbiBjb21vIEpveSBEaXZpc2lvbiBwbG90cy4gQWJham8gb3MgcG9uZ28gdW4gZWplbXBsbyBzYWNhZG8gZGUgbGEgW3ZpZ25ldHRlIGRlbCBwYXF1ZXRlIGdncmlkZ2VzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dyaWRnZXMvdmlnbmV0dGVzL2ludHJvZHVjdGlvbi5odG1sKS4KCgpgYGB7cn0KbGlicmFyeSh2aXJpZGlzKQpnZ3Bsb3QobGluY29sbl93ZWF0aGVyLCBhZXMoeCA9IGBNZWFuIFRlbXBlcmF0dXJlIFtGXWAsIHkgPSBgTW9udGhgLCBmaWxsID0gLi54Li4pKSArCiAgZ2VvbV9kZW5zaXR5X3JpZGdlc19ncmFkaWVudChzY2FsZSA9IDMsIHJlbF9taW5faGVpZ2h0ID0gMC4wMSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlRlbXAuIFtGXSIsIG9wdGlvbiA9ICJDIikgKwogIGxhYnModGl0bGUgPSAnVGVtcGVyYXR1cmVzIGluIExpbmNvbG4gTkUgaW4gMjAxNicpCmBgYAoKCjxicj4KCiMjIyA2LjIgU2NhdHRlciBwbG90CgpTY2F0dGVyIHBsb3QsIGdyw6FmaWNvIGRlIHB1bnRvcyBvIGdyw6FmaWNvIFgtWS4gU2UgdXRpbGl6YSBwYXJhIG1vc3RyYXIgbGEgKipyZWxhY2nDs24gZW50cmUgRE9TIHZhcmlhYmxlcyBjb250aW51YXMqKi4gTG8gdGVuZW1vcyBtw6FzIHF1ZSB2aXN0bywgeWEgcXVlIGVzIGVsIHRpcG8gZGUgZ3LDoWZpY28gcXVlIGhlbW9zIHV0aWxpemFkbyBkdXJhbnRlIHRvZG8gZWwgdHV0b3JpYWwuIAoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgsIGNvbG9yID0gU3BlY2llcykpICsgCiAgICAgZ2VvbV9wb2ludCgpICsKICAgICBsYWJzKHRpdGxlID0gIkdyw6FmaWNvIDE6IExvbmdpdHVkIGRlbCBzw6lwYWxvIGZyZW50ZSBhbCBww6l0YWxvIiwKICAgICAgIHN1YnRpdGxlID0gIihkaWZlcmVuY2lhbmRvIHBvciBlc3BlY2llIGRlIGxpcmlvKSIsCiAgICAgICBjYXB0aW9uID0gIkRhdG9zIHByb3ZlbmllbnRlcyBkZWwgSXJpcyBkYXRhc2V0IiwKICAgICAgIHggPSAiTG9uZ2l0dWQgZGVsIHPDqXBhbG8iLAogICAgICAgeSA9ICJMb25naXR1ZCBkZWwgcMOpdGFsbyIsCiAgICAgICBjb2xvciA9ICJFc3BlY2llIGRlIGxpcmlvIikKYGBgCgoKCiMjIyMgT3ZlcnBsb3R0aW5nCgpMb3MgZ3LDoWZpY29zIGRlIHB1bnRvcyBzZSB1c2FuIGhhYml0dWFsbWVudGUgcGFyYSBtb3N0cmFyIGxhIHJlbGFjacOzbiBlbnRyZSBkb3MgdmFyaWFibGVzIGNvbnRpbnVhcy4gRW4gY29uanVudG9zIGRlIGRhdG9zIGNvbiBtdWNoYXMgb2JzZXJ2YWNpb25lcyBwdWVkZW4gdGVuZXIgdW4gcHJvYmxlbWEgZGUgIm92ZXJwbG90dGluZyIuIEVzdGEgc2l0dWFjacOzbiBvY3VycmUgY3VhbmRvIHVub3MgcHVudG9zIHNlICBkaWJ1amFuIGVuY2ltYSBkZSBvdHJvcywgZGUgZm9ybWEgcXVlIG5vIHNlIGFwcmVjaWEgYmllbiBsYSByZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlcwoKSGF5IHZhcmlhcyBmb3JtYXMgZGUgdHJhdGFyIGVzdGUgcHJvYmxlbWEuIFZlw6Ftb3NsbyBjb24gdW4gZWplbXBsbyBzYWNhZG8gZGUgbGEgd2ViIGRlIGdncGxvdDI6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpzZXQuc2VlZCgxMjM0KQpkZiA8LSBkYXRhLmZyYW1lKHggPSBybm9ybSgyMDAwKSwgeSA9IHJub3JtKDIwMDApKSAgICMtIGNyZWFtb3MgdW4gY29uanV0byBkZSBkYXRvcyBjb24gMjAwMCBvYnNlcnZhY2lvbmVzIHkgMiB2LgoKcCA8LSBnZ3Bsb3QoZGYsIGFlcyh4LCB5KSkgKyB4bGFiKE5VTEwpICsgeWxhYihOVUxMKQoKcCArIGdlb21fcG9pbnQoKQpwICsgZ2VvbV9wb2ludChzaGFwZSA9IDEpICMgSG9sbG93IGNpcmNsZXMKcCArIGdlb21fcG9pbnQoc2hhcGUgPSAiLiIpICMgUGl4ZWwgc2l6ZWQKCiMtIEZvciBsYXJnZXIgZGF0YXNldHMgd2l0aCBtb3JlIG92ZXJwbG90dGluZywgeW91IGNhbiB1c2UgYWxwaGEgYmxlbmRpbmcgKHRyYW5zcGFyZW5jeSkgdApwICsgZ2VvbV9wb2ludChhbHBoYSA9IDEgLyAxMCkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjg1JSJ9CnNldC5zZWVkKDEyMzQpCmRmIDwtIGRhdGEuZnJhbWUoeCA9IHJub3JtKDIwMDApLCB5ID0gcm5vcm0oMjAwMCkpICAgIy0gY3JlYW1vcyB1biBjb25qdXRvIGRlIGRhdG9zIGNvbiAyMDAwIG9ic2VydmFjaW9uZXMgeSAyIHYuCgpwIDwtIGdncGxvdChkZiwgYWVzKHgsIHkpKSArIHhsYWIoTlVMTCkgKyB5bGFiKE5VTEwpCgpwMSA8LSBwICsgZ2VvbV9wb2ludCgpCnAyIDwtIHAgKyBnZW9tX3BvaW50KHNoYXBlID0gMSkgIyBIb2xsb3cgY2lyY2xlcwpwMyA8LSBwICsgZ2VvbV9wb2ludChzaGFwZSA9ICIuIikgIyBQaXhlbCBzaXplZAoKIy0gRm9yIGxhcmdlciBkYXRhc2V0cyB3aXRoIG1vcmUgb3ZlcnBsb3R0aW5nLCB5b3UgY2FuIHVzZSBhbHBoYSBibGVuZGluZyAodHJhbnNwYXJlbmN5KSB0CnA0IDwtIHAgKyBnZW9tX3BvaW50KGFscGhhID0gMSAvIDEwKQoKcDEgKyBwMiArIHAzICsgcDQgKyBwbG90X2xheW91dChuY29sID0gMikKYGBgCgoKPGJyPgoKCkRlIGZvcm1hIGFsdGVybmF0aXZhLCBwb2RlbW9zIGxpZGlhciBjb24gZWwgb3ZlcnBsb3R0aW5nIHV0aWxpemFuZG8gb3RybyBlbmZvcXVlIHF1ZSBjb25zaXN0ZSBlbiBwZW5zYXIgZW4gZWwgb3ZlcnBsb3R0aW5nIGNvbW8gdW4gcHJvYmxlbWEgZGUgZGVuc2lkYWQgZW4gMiBkaW1lbnNpb25lcyB5IHV0aWxpemFyIHVuIGhleC1wbG90LiBFbiB1biBoZXgtcGxvdCBzZSByZXByZXNlbnRhbiByZWdpb25lcyAoZ2VuZXJhbG1lbnRlIGhleGFnb25hbGVzKSBjb2xvcmVhZGFzIGVuIGZ1bmNpw7NuIGRlbCBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgcXVlIGNhZW4gZW4gY2FkYSByZWdpw7NuLCBkZSBmb3JtYSBxdWUgZXMgZXF1aXZhbGVudGUgYSB1biBoaXN0b2dyYW1hIHBlcm8gZW4gMmQuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwICsgZ2VvbV9iaW4yZCgpCnAgKyBnZW9tX2JpbjJkKGJpbnMgPSAxMCkKcCArIGdlb21faGV4KCkKcCArIGdlb21faGV4KCkgKyBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAiIzEzMkI0MyIsIGhpZ2ggPSAiIzU2QjFGNyIpICAKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjk1JSJ9CnAxIDwtIHAgKyBnZW9tX2JpbjJkKCkKcDIgPC0gcCArIGdlb21fYmluMmQoYmlucyA9IDEwKQpwMyA8LSBwICsgZ2VvbV9oZXgoKQpwNCA8LSBwICsgZ2VvbV9oZXgoKSArIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICIjMTMyQjQzIiwgaGlnaCA9ICIjNTZCMUY3IikgIApwMSArIHAyICsgcDMgKyBwNCArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKCgoKIAoKRW4gW2VzdGUgcG9zdF0oaHR0cHM6Ly9jdXIuYXQvbnVoWWtMMD9tPWVtYWlsJnNpZD05SXByVEN0KSB1dGlsaXphbiBoZXgtcGxvdHMgcGFyYSBtb3N0cmFyIGxhcyB6b25hcyBkZXNkZSBkb25kZSB0aXJhbiBhIGNhbmFzdGEgbG9zIGp1Z2Fkb3JlcyBOQkEuIEVuIFtlc3RlIG90cm8gcG9zdF0oaHR0cHM6Ly9yY3Jhc3RpbmF0ZS5yYmluZC5pby9wb3N0L3RoZXJlLWlzLWEtZ2FtZS1pLXBsYXktYW5hbHl6aW5nLW1ldGFjcml0aWMtc2NvcmVzLWZvci12aWRlby1nYW1lcy8pIHV0aWxpemFuIHVuYSBjb21iaW5hY2nDs24gZGUgYGdlb21fcG9pbnQoYWxwaGEgPSAuMiwgcGNoID0gMTUpICsgZ2VvbV9kZW5zaXR5XzJkKClgIHBhcmEgYW5hbGl6YXIgbGFzIHB1bnR1YWNpb25lcyBkZSB2aWRlb2p1ZWdvcy4gQWRlbcOhcyBoYWNlIHVuIHVzbyBhdmFuemFkbyBkZSBsYXMgZXNjYWxhcywgYWwgbWVub3MgcGFyYSBtaSwgcGFyYSBxdWUgZWwgZ3LDoWZpY28gc2VhIGbDoWNpbCBkZSB2aXN1YWxpemFyLgoKQWNhYmEgZGUgYXBhcmVjZXIgdW4gbnVldm8gZ2VvbTogW2BnZW9tX3BvaW50ZGVuc2l0eSgpYF0oaHR0cHM6Ly9naXRodWIuY29tL0xLcmVtZXIvZ2dwb2ludGRlbnNpdHkpLiBDb21vIGRpY2VuIGVuIHN1IHJlcG8gZGUgR2l0aHViOiAiQSBjcm9zcyBiZXR3ZWVuIGEgc2NhdHRlciBwbG90IGFuZCBhIDJEIGRlbnNpdHkgcGxvdCIuCgoKYGBge3J9CiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoIkxLcmVtZXIvZ2dwb2ludGRlbnNpdHkiKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkoZ2dwb2ludGRlbnNpdHkpCnAgKyAgZ2VvbV9wb2ludGRlbnNpdHkoYWRqdXN0ID0gNykgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKQoKYGBgCgoKU2kgcXVpZXJlcyBzYWJlciBtw6FzIGNvc2FzIHNvYnJlIGPDs21vIGhhY2VyIHNjYXR0ZXJwbG90cyBjb24gYGdncGxvdDJgIHB1ZWRlcyBoYWNlcmxvIHBvciBlamVtcGxvLCBbYXF1w61dKGh0dHA6Ly93d3cuY29va2Jvb2stci5jb20vR3JhcGhzL1NjYXR0ZXJwbG90c18oZ2dwbG90MikvKSBvIFthcXXDrV0oaHR0cHM6Ly9taWNoYWVsdG90aC5tZS9hLWRldGFpbGVkLWd1aWRlLXRvLXRoZS1nZ3Bsb3Qtc2NhdHRlci1wbG90LWluLXIuaHRtbCkuCgo8YnI+CgojIyMgNi4zIEJveHBsb3QgKGdyw6FmaWNvcyBkZSBjYWphKQoKUGFyYSB2aXN1YWxpemFyIHVuYSB2YXJpYWJsZSBjdWFudGl0YXRpdmEgc2Ugc3VlbGVuIHVzYXIgbG9zIGhpc3RvZ3JhbWFzLCBwZXJvIHNpIGxvIHF1ZSBxdWllcmVzIGVzIHZpc3VhbGl6YXIgY29tbyB2YXLDrWFuIGxvcyB2YWxvcmVzIGRlIHVuYSB2YXJpYWJsZSBjb250aW51YSBlbiBmdW5jacOzbiBkZSBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgKFNwZWNpZXMpIHNlIHN1ZWxlbiB1c2FuIGJveHBsb3RzIG8gZGlhZ3JhbWFzIGRlIGNhamEuIFNvbiDDunRpbGVzIHBhcmEgY29tcGFyYXIgZGlmZXJlbnRlcyBncnVwb3MgeSBwYXJhIGlkZW50aWZpY2FyIG91dGxpZXJzLgoKClBhcmEgaGFjZXIgdW4gYm94cGxvdCBvIGRpYWdyYW1hIGRlIGNhamEgc2UgdXRpbGl6YSBgZ2VvbV9ib3hwbG90KClgLiBFbCBncsOhZmljbyBtdWVzdHJhIDUgZXN0YWTDrXN0aWNvczogbGEgbWVkaWFuYSBjb24gdW5hIGxpbmVhIGdydWVzYSwgZWwgcHJpbWVyIHkgdGVyY2VyIGN1YXJ0aWwgZGUgbG9zIGRhdG9zIGNvbiBsb3MgbGltaXRlcyBkZSBsYSBjYWphIHkgZWwgbcOheGltbyB5IGVsIG3DrW5pbW8gKGxvcyBsaW1pdGVzIGRlIGxhIGxpbmVhIHZlcnRpY2FsKS4gQWRpY2lvbmFsbWVudGUsIHNpIGV4aXN0ZW4gb3V0bGllcnMsIGVzdG9zIHRhbWJpw6luIHNlIHJlcHJlc2VudGFyw6FuLgoKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKHggPSBTcGVjaWVzLCAgeSA9IFNlcGFsLkxlbmd0aCkpICsgZ2VvbV9ib3hwbG90KCkgCmBgYAoKU2UgcHVlZGVuIGNhbWJpYXIgYWxndW5hcyBvcGNpb25lcyBkZWwgZ3LDoWZpY28KCmBgYHtyfQpwIDwtIGdncGxvdChpcmlzLCBhZXMoeCA9IFNwZWNpZXMsICB5ID0gU2VwYWwuTGVuZ3RoKSkgCnAgKyBnZW9tX2JveHBsb3QoYWVzKGZpbGwgPSBTcGVjaWVzKSwgb3V0bGllci5jb2xvdXIgPSAicHVycGxlIikKYGBgCgoKQSB2ZWNlcywgcGFyYSBtZWpvcmFyIGxhIHZpc3VhbGl6YWNpw7NuLCBjb252aWVuZSByb3RhciBsb3MgZWplcy4gUG9kZW1vcyBoYWNlcmxvIGNvbiBgY29vcmRfZmxpcCgpYAoKYGBge3J9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyh4ID0gU3BlY2llcywgIHkgPSBTZXBhbC5MZW5ndGgpKSArIGdlb21fYm94cGxvdChhZXMoZmlsbCA9IFNwZWNpZXMpKSAKcCArIGNvb3JkX2ZsaXAoKQpgYGAKCgpQYXJhIG1lam9yYXIgbGEgdmlzdWFsaXphY2nDs24gcG9kZW1vcyBpbmNsdWlyIGxhcyBvYnNlcnZhY2lvbmVzIG9yaWdpbmFsZXMgY29uIGBnZW9tX3BvaW50KClgLCBwZXJvIGhhYnLDrWEgbXVjaG8gb3ZlcnBsb3R0aW5nLCBhc8OtIHF1ZSBtZWpvciBjb24gYGdlb21faml0dGVyKClgCgoKYGBge3J9CnAgKyBnZW9tX2ppdHRlcih3aWR0aCA9IDAuMTUsIGFscGhhID0gMS80LCBjb2xvciA9ICJ0b21hdG8iKQpgYGAKClBvZGVtb3MgYcOxYWRpciBjb24gYHN0YXRzKClgIGFsZ8O6biBlc3RhZMOtc3RpY28gbcOhcyBjb24gYHN0YXRzX3h4KClgLCBwb3IgZWplbXBsbyBsYSBtZWRpYSBjb24gYHN0YXRzX3N1bW1hcnlgOgoKCmBgYHtyfQpwICsgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBnZW9tID0gInBvaW50IiwgY29sb3IgPSAicHVycGxlIiwgc2l6ZSA9IDIuNSkKYGBgCgpMb3MgYm94cGxvdHMgcmVzdW1lbiBsYSBkaXN0cmlidWNpw7NuIGRlIHVuYSB2YXJpYWJsZSBjdWFudGl0YXRpdmEgY29uIHPDs2xvIGNpbmNvIG7Dum1lcm9zLCBwcm9wb3JjaW9uYW5kbyB1biByZXN1bWVuIMO6dGlsIGRlIGxvcyBkYXRvcywgcGVybyBvY3VsdGFuIGxhIGZvcm1hIGRlIGxhIGRpc3RyaWJ1Y2nDs247IHBvciBlamVtcGxvLCBzaSBsYSBkaXN0cmlidWNpw7NuIGZ1ZXNlIGJpbW9kYWwsIG5vIGxvIGFwcmVjaWFyw61hbW9zLiBBZGVtw6FzLCBhdW5xdWUgdXNlbW9zIGBnZW9tX2ppdHRlcigpYCBwYXJhIHN1cGVycG9uZXIgbG9zIHZhbG9yZXMgb3JpZ2luYWxlcywgYSB2ZWNlcyAgZXMgZGlmw61jaWwgYSBzaW1wbGUgdmlzdGEgaW5mZXJpciBsYSBkaXN0cmlidWNpw7NuIGRlIGVzdG9zLiBQb3IgZWxsbyBoYXkgdmFyaWFzIHTDqWNuaWNhcy9nZW9tcyDDunRpbGVzIHF1ZSBheXVkYW4gYSByZXNvbHZlciBlbCBwcm9ibGVtYSwgcG9yIGVqZW1wbG8gYGdlb21fdmlvbGluKClgLiBVbmEgYWx0ZXJuYXRpdmEgYWwgYm94cGxvdCBzb24gbG9zIGdyw6FmaWNvcyBkZSB2aW9sw61uIGRvbmRlIHNlIGVzdGltYSB5IG11ZXN0cmEgbGEgZm9ybWEgZGUgbGEgZGlzdHJpYnVjacOzbiBkZSBsYXMgb2JzZXJ2YWNpb25lczoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyh4ID0gU3BlY2llcywgIHkgPSBTZXBhbC5MZW5ndGgpKSAKcCArIGdlb21fdmlvbGluKGFlcyhmaWxsID0gU3BlY2llcyksIGFscGhhID0gMC42KQpwICsgZ2VvbV92aW9saW4oYWVzKGZpbGwgPSBTcGVjaWVzKSwgYWxwaGEgPSAwLjYpICsgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjE1LCBhbHBoYSA9IDEvNCkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwJSJ9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyh4ID0gU3BlY2llcywgIHkgPSBTZXBhbC5MZW5ndGgpKSAKcDEgPC0gcCArIGdlb21fdmlvbGluKGFlcyhmaWxsID0gU3BlY2llcyksIGFscGhhID0gMC42KQpwMiA8LSBwICsgZ2VvbV92aW9saW4oYWVzKGZpbGwgPSBTcGVjaWVzKSwgYWxwaGEgPSAwLjYpICsgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjE1LCBhbHBoYSA9IDEvNCkKcDEgKyBwMiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKCjxicj4KCkVuIGVsIGVqZW1wbG8gcXVlIGhlbW9zIHVzYWRvIGhlbW9zIHRlbmlkbyBzdWVydGUgeSBsb3MgdHJlcyBncnVwb3MgZXN0YWJhbiBvcmRlbmFkb3MsIHBlcm8gb3RyYXMgdmVjZXMgbm8gdGVuZHJlbW9zIHRhbnRhIHN1ZXJ0ZSwgcG9yIGVqZW1wbG8gc2kgZW4gbHVnYXIgZGUgZ3JhZmljYXIgbGEgbG9uZ2l0dWQgZGUgc8OpcGFsbyBxdWVyZW1vcyB2aXN1YWxpemFyIGxhIGFuY2h1cmEgKFNlcGFsLldpZHRoKToKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKHggPSBTcGVjaWVzLCAgeSA9IFNlcGFsLldpZHRoKSkgKyBnZW9tX2JveHBsb3QoKQpgYGAKCsK/Q8OzbW8gb3JkZW5hbW9zIGxvcyBncnVwb3M/IFBvciBlamVtcGxvIHBvZGVtb3Mgb3JkZW5hcmxvcyBkZSBtZW5vciBhIG1heW9yIGVuIGZ1bmNpw7NuIGRlIHN1IGFuY2h1cmEgbWVkaWEgZGVsIHPDqXBhbG8uIFBhcmEgZWxsbyB1c2Ftb3MgYHN0YXRzOjpyZW9yZGVyKClgCgoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoeCA9IHJlb3JkZXIoU3BlY2llcywgU2VwYWwuV2lkdGgsIG1lYW4pLCAgeSA9IFNlcGFsLldpZHRoKSkgKyBnZW9tX2JveHBsb3QoKSArCiAgeGxhYigiRGUgbWVub3IgYSBtYXlvciBhbmNodXJhIGRlbCBzw6lwYWxvIikKYGBgCgoKCgoKICAKIyMjIDYuNCBHcsOhZmljb3MgZGUgYmFycmFzCgpMb3MgZ3LDoWZpY29zIGRlIGJhcnJhcyBzZSB1dGlsaXphbiBwYXJhIHJlcHJlc2VudGFyIHVuYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSwgY29tbyBwb3IgZWplbXBsbyBTcGVjaWVzLCBvIHZhcmlhYmxlcyBjdWFudGl0YXRpdmFzIGRpc2NyZXRhcy4gU2UgcmVwcmVzZW50YW4gYmFycmFzIHZlcnRpY2FsZXMgcHJvcG9yY2lvbmFsZXMgYSBsb3MgdmFsb3JlcyBkZSBsYSB2YXJpYWJsZSBlbiBjYWRhIGNhdGVnb3LDrWEgbyB2YWxvci4gUGFyYSBjcmVhciBncsOhZmljb3MgZGUgYmFycmFzIGNvbiBnZ3Bsb3QyIHNlIHVzYSBgZ2VvbV9iYXIoKWAuCgpQYXJhIGhhY2VyIG51ZXN0cm8gcHJpbWVyIGdyw6FmaWNvIGRlIGJhcnJhcyBubyB2YW1vcyBhIHV0aWxpemFyIGBpcmlzYCBwb3JxdWUgZW4gbGEgw7puaWNhIHZhcmlhYmxlIGNhdGVnw7NyaWNhIChTcGVjaWVzKSByZXN1bHRhIHF1ZSBoYXkgNTAgbGlyaW9zIGVuIGNhZGEgdGlwbyBkZSBlc3BlY2llLiBVc2FyZW1vcyBlbCBkYXRhLmZyYW1lIGBtcGdgIGRlbCBwYXF1ZXRlIGdncGxvdDIgcXVlIGNvbnRpZW5lIDIzNCBvYnNlcnZhY2lvbmVzIHNvYnJlIGRpc3RpbnRvcyBtb2RlbG9zIGRlIGNvY2hlcyB5IHN1cyBjYXJhY3RlcsOtc3RpY2FzLiBBbGd1bm9zIGRlIGxvcyBlamVtcGxvcyBlc3TDoW4gc2FjYWRvcyBkZSBsYSBbd2ViIGRlIGdncGxvdDJdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZW9tX2Jhci5odG1sKS4KCmBgYHtyLCBldmFsID0gRkFMU0V9CnAgPC0gZ2dwbG90KG1wZywgYWVzKGNsYXNzKSkKcCArIGdlb21fYmFyKCkKcCArIGdlb21fYmFyKGZpbGwgPSAic3RlZWxibHVlIikgKyBjb29yZF9mbGlwKCkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjk1JSIsIGZpZy5hc3AgPSA2Lzl9CnAgPC0gZ2dwbG90KG1wZywgYWVzKGNsYXNzKSkKcDEgPC0gcCArIGdlb21fYmFyKCkKcDIgPC0gcCArIGdlb21fYmFyKGZpbGwgPSAic3RlZWxibHVlIikgKyBjb29yZF9mbGlwKCkKcDEgKyBwMiArIHBsb3RfbGF5b3V0KG5jb2wgPSAyKQpgYGAKCgpDb21vIHZlbW9zLCBsYSB2YXJpYWJsZSBgY2xhc3NgIGVzIGNhdGVnw7NyaWNhLCBjb25jcmV0YW1lbnRlIHRpZW5lIDcgZ3J1cG9zIG8gY2F0ZWdvcsOtYXMgZGUgY29jaGVzLiBMYXMgYmFycmFzIHZlcnRpY2FsZXMgc29uIHByb3BvcmNpb25hbGVzIGFsIG7Dum1lcm8gZGUgdmVow61jdWxvcyBlbiBjYWRhIGNhdGVnb3LDrWEuIENvbiBgY29vcmRfZmxpcCgpYCBsYXMgY2F0ZWdvcsOtYXMgcGFzYW4gYSByZXByZXNlbnRhcnNlIGVuIGVsIGVqZSBZLCBoYWNpZW5kbyBxdWUsIGdlbmVyYWxtZW50ZSwgc2UgdmlzdWFsaWNlbiBtZWpvciBsb3Mgbm9tYnJlcyBkZSBsYXMgY2F0ZWdvcsOtYXMuCgoKCjxicj4KClNpIGVuIGx1Z2FyIGRlIHRlbmVyIHVuYSB0YWJsYSBkZSBkYXRvcywgdGVuZW1vcyB5YSB1bmEgKip0YWJsYSBkZSBmcmVjdWVuY2lhcyoqLCB0ZW5kcmVtb3MgcXVlIGVzcGVjaWZpY2FyIGVuIGBhZXMoKWAgcXVlIGxhIHZhcmlhYmxlIGNvbiBsYXMgZnJlY3VlbmNpYXMgc2UgbWFwZWUvYXNvY2llIGFsIGVqZSBZOiBgYWVzKHggPSB2YXJpYWJsZSwgeSA9IGZyZWN1ZW5jaWFzKWAuIEFkZW3DoXMgdGVuZHLDoXMgcXVlIHVzYXIgYGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKWAgbyBkaXJlY3RhbWVudGUgdXNhciBgZ2VvbV9jb2woKWAgcXVlIHlhIHVzYSBwb3IgZGVmZWN0byBzdGF0X2lkZW50aXR5KCkuIFZlw6Ftb3NsbyBjb24gdW4gZWplbXBsbzoKCgpgYGB7cn0KZGYgPC0gbXBnICU+JSBncm91cF9ieShjbGFzcykgJT4lIGNvdW50CnAgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IGNsYXNzLCB5ID0gbikpCnAgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgIGZpbGwgPSAic3RlZWxibHVlIikgCiMgcCArIGdlb21fY29sKGZpbGwgPSAic3RlZWxibHVlIikgICAgICAgICAgICAgICAgICAgICAgICAgICMtIGhhY2UgZXhhY3RhbWVudGUgZWwgbWlzbW8gcGxvdApgYGAKCjxicj4KCiMjIyMgRGlzdGludGFzIHBvc2ljaW9uZXMgcGFyYSBsYXMgYmFycmFzCgpQYXJhIGVzdGEgc3Vic2VjY2nDs24gdXRpbGl6YXJlbW9zIGVsIGRhdGEuZnJhbWUgYG10Y2Fyc2AKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChtdGNhcnMsIGFlcyhmYWN0b3IoY3lsKSwgZmlsbCA9IGZhY3Rvcih2cykpKSArIGdlb21fYmFyKCkgIy0gcG9zCmdncGxvdChtdGNhcnMsIGFlcyhmYWN0b3IoY3lsKSwgZmlsbCA9IGZhY3Rvcih2cykpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKQpnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpCmdncGxvdChtdGNhcnMsIGFlcyhmYWN0b3IoY3lsKSwgZmlsbCA9IGZhY3Rvcih2cykpKSArIGdlb21fYmFyKHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UyKHByZXNlcnZlID0gInNpbmdsZSIpKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiMTAwJSIsICBmaWcuYXNwID0gNS85fQpwMSA8LSBnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2JhcigpICMtIHBvcwpwMiA8LSBnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikKcDMgPC0gZ2dwbG90KG10Y2FycywgYWVzKGZhY3RvcihjeWwpLCBmaWxsID0gZmFjdG9yKHZzKSkpICsgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKQpwNCA8LSBnZ3Bsb3QobXRjYXJzLCBhZXMoZmFjdG9yKGN5bCksIGZpbGwgPSBmYWN0b3IodnMpKSkgKyBnZW9tX2Jhcihwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHByZXNlcnZlID0gInNpbmdsZSIpKQoKcDEgKyBwMiArIHAzICsgcDQgKyBwbG90X2xheW91dChuY29sID0gMikKYGBgCgoKPGJyPgoKIyMjIyBSZW9yZGVuYW5kbyBsYXMgY2F0ZWdvcsOtYXMKCkEgdmVjZXMgZXMgaW1wb3J0YW50ZSByZW9yZGVuYXIgbGFzIGJhcnJhcyBwYXJhIG1lam9yYXIgbGEgdmlzdWFsaXphY2nDs24uIFBhcmEgZWxsbyB0ZW5kcmVtb3MgcXVlIHVzYXIgKipmYWN0b3JlcyoqLiBQb3IgZWplbXBsbywgdm9sdmllbmRvIGFsIGBtcGdgIGRhdGFzZXQ6IAoKCmBgYHtyfQpkZiA8LSBtcGcKZGYgPC0gZGYgJT4lIG11dGF0ZShjbGFzcyA9IGZvcmNhdHM6OmFzX2ZhY3RvcihjbGFzcykpICMtIGNvbnZlcnRpbW9zIGxhIHYuIGNsYXNzIGEgZmFjdG9yIGNvbiBsYSBmLiBhc19mYWN0b3IoKQpkZiA8LSBkZiAlPiUgbXV0YXRlKGNsYXNzID0gZm9yY2F0czo6ZmN0X2luZnJlcShjbGFzcykpICMtIGZjdF9pbmZyZXEoKSBsb3Mgbml2ZWxlcyBkZWwgZmFjdG9yIHNlZ8O6biBzdSBmcmVjdWVuY2lhIGRlIG1heW9yIGEgbWVub3IKcCA8LSBnZ3Bsb3QoZGYsIGFlcyhmY3RfcmV2KGNsYXNzKSkpICMtIGZjdF9yZXYoKSBvcmRlbmEgbG9zIGxldmVscyBkZSBtZW5vciBhIG1heW9yCnAgKyBnZW9tX2JhcihmaWxsID0gInN0ZWVsYmx1ZSIpICsgY29vcmRfZmxpcCgpCmBgYAoKPGJyPgoKU2kgcXVlcmVtb3MgcXVlIGVuIGxhcyBiYXJyYXMgc2UgdmlzdWFsaWNlIGVsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyBvIGZyZWN1ZW5jaWEgYWJzb2x1dGEgZGUgY2FkYSBjYXRlZ29yw61hOgoKCmBgYHtyfQpwICsgZ2VvbV9iYXIoZmlsbCA9ICJzdGVlbGJsdWUiKSArIGNvb3JkX2ZsaXAoKSArCiAgICBnZW9tX3RleHQoc3RhdD0nY291bnQnLCBhZXMobGFiZWwgPSAuLmNvdW50Li4gKSwgaGp1c3QgPSAtMC4xNSwgc2l6ZSA9IDMuMjUpIApgYGAKCgoKPGJyPgoKIyMjIyBQb3JjZW50YWplcyBlbiBsdWdhciBkZSBjb3VudHMKIApTaSBxdWVyZW1vcyBxdWUgbGFzIGJhcnJhcyByZXByZXNlbnRlbiBwb3JjZW50YWplcyBlbiBsdWdhciBkZSBudW1lcm8gZGUgY2Fzb3MgZW4gY2FkYSBjYXRlZ29yw61hOgoKCmBgYHtyfQpwIDwtIGdncGxvdChkZiwgYWVzKGZjdF9yZXYoY2xhc3MpKSkgIy0gZmN0X3JldigpIG9yZGVuYSBsb3MgbGV2ZWxzIGRlIG1lbm9yIGEgbWF5b3IKcCArIGdlb21fYmFyKGFlcyggeSA9ICguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSwgZmlsbCA9ICJzdGVlbGJsdWUiKSAgKyBjb29yZF9mbGlwKCkKYGBgCgoKRW4gcmVhbGlkYWQgZXN0byBtaXNtbyBzZSBwdWRlIGhhY2VyIGRlIHZhcmlhcyBmb3JtYXMuIEVuIFtlc3RlIHBvc3RdKGh0dHBzOi8vc2ViYXN0aWFuc2F1ZXIuZ2l0aHViLmlvL3BlcmNlbnRhZ2VfcGxvdF9nZ3Bsb3QyX1YyLykgbm9zIG11ZXN0cmFuIDUgZm9ybWFzIGRlIGhhY2VybG8uCgoKPGJyPgogU2kgcXVpZXJlcyBzYWJlciBtw6FzIHNvYnJlIGNvbW8gaGFjZXIgZ3LDoWZpY29zIGRlIGJhcnJhcyBjb24gZ2dwbG90MiwgcHVlZGVzIGhhY2VybG8gW2FxdcOtXSgKaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC93aWtpL2dncGxvdDItYmFycGxvdHMtcXVpY2stc3RhcnQtZ3VpZGUtci1zb2Z0d2FyZS1hbmQtZGF0YS12aXN1YWxpemF0aW9uCikgbyBbYXF1w61dKGh0dHBzOi8vd3d3LnItZ3JhcGgtZ2FsbGVyeS5jb20vNDgtZ3JvdXBlZC1iYXJwbG90LXdpdGgtZ2dwbG90Mi5odG1sKS4KCgo8YnI+CgoKIyMjIDYuNSBHcsOhZmljb3MgZGUgbGluZWFzCgpMb3MgZ3LDoWZpY29zIGRlIGxpbmVhcyBzZSB1c2FuIHByaW5jaXBhbG1lbnRlIHBhcmEgbW9zdHJhciBsYSBldm9sdWNpw7NuIGRlIHZhcmlhYmxlcyBlbiBlbCB0aWVtcG8uIEdlbmVyYWxtZW50ZSBsYSB2YXJpYWJsZSBxdWUgc2UgbXVlc3RyYSBlbiBlbCBlamUgWCBlcyBlbCB0aWVtcG8uIEVuIEVjb25vbcOtYSBlc3RvcyBncsOhZmljb3MgcHVlZGUgcXVlIHNlYW4gbG9zIG3DoXMgdXNhZG9zIHkgaGFiaXR1YWxtZW50ZSBub3MgcmVmZXJpbW9zIGEgZWxsb3MgY29tbyBncsOhZmljb3MgdGVtcG9yYWxlcy4KCgpQb2RlbW9zIHNpbXVsYXIgZ3LDoWZpY29zIGRlIHRpZW1wbyBjb24gYGdlb21fbGluZSgpYCB5IGBnZW9tX3BhdGgoKWAuIEVzdG9zIDIgZ2VvbXMgZ3JhZmljYW4gbGluZWFzIGVudHJlIGRvcyBvYnNlcnZhY2lvbmVzIGRlIHVuYSB2YXJpYWJsZS4gUG9yIGVqZW1wbG86CgoKYGBge3J9CmdncGxvdChlY29ub21pY3MsIGFlcyhkYXRlLCB1ZW1wbWVkKSkgKyBnZW9tX2xpbmUoKQpgYGAKCgpFbXBlY2Vtb3MgdXNhbmRvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGBnYXBtaW5kZXJgIHBhcmEgbW9zdHJhciBsYXMgb2JzZXJ2YWNpb25lcyBkZSBsYSB2YXJpYWJsZSBsaWZlRXhwIHBhcmEgRXNwYcOxYToKCmBgYHtyfQpsaWJyYXJ5KGdhcG1pbmRlcikKZ2FwbWluZGVyICU+JSBmaWx0ZXIoY291bnRyeSA9PSAiU3BhaW4iKSAlPiUgCmdncGxvdChhZXMoeCA9IHllYXIsIHkgPSBsaWZlRXhwKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKQpgYGAKCgpWaXN1YWxpY2Vtb3MgYWhvcmEgNCBzZXJpZXMgZGUgdGllbXBvLCBsYSBlc3BlcmFuemEgZGUgdmlkYSBlbiBjdWF0cm8gcGHDrXNlcyBldXJvcGVvcy4gUGFyYSBwb2RlciBkaXN0aW5ndWlyIGxhcyBsaW5lYXMgZGUgY2FkYSBwYcOtcyBwb2RlbW9zIGFzb2NpYXIgbGEgdmFyaWFibGUgY291bnRyeSBhIGxhIGNhcmFjdGVyw61zdGljYSBlc3TDqXRpY2EgY29sb3I6IGBhZXMoY29sb3IgPSBjb3VudHJ5KWAKCmBgYHtyfQpnYXBtaW5kZXIgJT4lIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiU3BhaW4iLCAiRnJhbmNlIiwgIk5vcndheSIsICJCZWxnaXVtIikpICU+JSAKZ2dwbG90KGFlcyh4ID0geWVhciwgeSA9IGxpZmVFeHAsIGNvbG9yID0gY291bnRyeSkpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkKYGBgCgpVbmEgdG9udGVyw61hIHBlcm8gcXVlIHB1ZWRlIHNlciBkZSB1dGlsaWRhZCBwYXJhIHNhYmVyIGN1YW50YSBkaWZlcmVuY2lhIGhheSBhbCBmaW5hbCBkZWwgcGxvdC4gTG8gYXByZW5kw60gW2FxdcOtXShodHRwczovL2Ryc2ltb25qLnN2YnRsZS5jb20vbGFiZWwtbGluZS1lbmRzLWluLXRpbWUtc2VyaWVzLXdpdGgtZ2dwbG90MikKCgpgYGB7cn0KZGYgPC0gZ2FwbWluZGVyICU+JSBmaWx0ZXIoY291bnRyeSAlaW4lIGMoIlNwYWluIiwgIkZyYW5jZSIsICJOb3J3YXkiLCAiQmVsZ2l1bSIpKSAKbGlmZUV4cF9lbmRzIDwtIGRmICU+JSBncm91cF9ieShjb3VudHJ5KSAlPiUgdG9wX24oMSwgeWVhcikgJT4lIHB1bGwobGlmZUV4cCkgIy0gdmVjdG9yIGNvbiBsb3MgdmFsb3JlcyDDumx0aW1vcyBkZSBsaWZlRXhwCmdncGxvdChkZiwgYWVzKHggPSB5ZWFyLCB5ID0gbGlmZUV4cCwgY29sb3IgPSBjb3VudHJ5KSkgKyBnZW9tX2xpbmUoKSArIAogICAgIHNjYWxlX3lfY29udGludW91cyhzZWMuYXhpcyA9IHNlY19heGlzKH4gLiwgYnJlYWtzID0gbGlmZUV4cF9lbmRzKSkgKyAgICMtIHNlY19heGlzKCkgZXNwZWNpZmljYSB1biBlamUgc2VjdW5kYXJpbwogICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKQpgYGAgCgo8YnI+CgpFbCBzaWd1aWVudGUgZWplbXBsbyBlc3TDoSBzYWNhZG8gZGUgW2VzdGUgbGlicm9dKGh0dHBzOi8vZWRhdi5pbmZvL3RpbWVzZXJpZXNiYXNpYy5odG1sKSBhw7puIGVuIGNvbnN0cnVjY2nDs24uIFV0aWxpemEgZWwgcGFxdWV0ZSBbdGlkeXF1YW50XShodHRwczovL2dpdGh1Yi5jb20vYnVzaW5lc3Mtc2NpZW5jZS90aWR5cXVhbnQpIHBhcmEgZGVzY2FyZ2FyIGxhcyBjb3RpemFjaW9uZXMgZGUgbGFzIDQgcHJpbmNpcGFsZXMgZW1wcmVzYXMgdGVjbm9sw7NnaWNhcywgbGFzIEdBRkEuIGB0aWR5cXVhbnRgIHRpZW5lIG11Y2hhcyBmdW5jaW9uZXMgaW50ZXJlc2FudGVzLCBwdWVkZXMgdmVybGFzIGNvcnJpZW5kbyBgdHFfdHJhbnNtdXRlX2Z1bl9vcHRpb25zKClgLgoKYGBge3J9CmxpYnJhcnkodGlkeXF1YW50KQpzdG9ja3MgPC0gYygiR09PR0wiLCJBTVpOIiwiRkIiLCJBQVBMIikgIy0gc2VsZWNjaW9uYW1vcyBhIGxhcyBHQUZBcwpkZiA8LSB0cV9nZXQoc3RvY2tzLCBmcm9tID0gYXMuRGF0ZSgiMjAxMy0wMS0wMSIpLCB0byA9IGFzLkRhdGUoIjIwMTMtMTItMzEiKSkKCmdncGxvdChkZiwgYWVzKGRhdGUsIHkgPSBjbG9zZSwgY29sb3IgPSBmY3RfcmVvcmRlcjIoc3ltYm9sLCBkYXRlLCBjbG9zZSkpKSArCiAgZ2VvbV9saW5lKCkgKyB4bGFiKCIiKSArIHlsYWIoIiIpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKUmVlc2NhbGFtb3MgcGFyYSBxdWUgbGFzIHNlcmllcyBjb21pZW5jZW4gdG9kYXMgZW4gMTAwCgpgYGB7cn0KZGYgPC0gZGYgJT4lIGdyb3VwX2J5KHN5bWJvbCkgJT4lIG11dGF0ZShyZXNjYWxlZF9jbG9zZSA9IDEwMCpjbG9zZSAvIGNsb3NlWzFdKQoKZ2dwbG90KGRmLCBhZXMoZGF0ZSwgeSA9IHJlc2NhbGVkX2Nsb3NlLCBjb2xvciA9IGZjdF9yZW9yZGVyMihzeW1ib2wsIGRhdGUsIHJlc2NhbGVkX2Nsb3NlKSkpICsKICBnZW9tX2xpbmUoKSArIHhsYWIoIiIpICsgeWxhYigiIikgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgo8YnI+CgpPdHJvIHBhcXVldGUgbXV5IMO6dGlsIHBhcmEgYW5hbGl6YXIgeSBkZXNjYXJnYXIgZGF0b3MgZmluYW5jaWVyb3MgZXMgW2BxdWFudG1vZGBdKGh0dHA6Ly93d3cucXVhbnRtb2QuY29tLykuIEVsIGVqZW1wbG8gc2lndWllbnRlIGxvIGhlIHNhY2FkbyBkZSBbZXN0ZSBmYW50YXN0aWNvIGxpYnJvXShodHRwczovL3NtYWMtZ3JvdXAuZ2l0aHViLmlvL2RzL2RhdGEuaHRtbCNleGFtcGxlLWFwcGxlLXN0b2NrLXByaWNlKS4KCgpgYGB7cn0KbGlicmFyeShxdWFudG1vZCkKdG9kYXkgPC0gU3lzLkRhdGUoKQp0aHJlZV9tb250aHNfYWdvIDwtIHNlcSh0b2RheSwgbGVuZ3RoID0gMiwgYnkgPSAiLTMgbW9udGhzIilbMl0KZ2V0U3ltYm9scygiQUFQTCIsIGZyb20gPSB0aHJlZV9tb250aHNfYWdvLCB0byA9IHRvZGF5KQpjYW5kbGVDaGFydChBQVBMLCB0aGVtZSA9ICd3aGl0ZScsIHR5cGUgPSAnY2FuZGxlcycpCmBgYAoKPGJyPgoKU2kgcXVpZXJlcyBzYWJlciBtw6FzIHNvYnJlIGdyw6FmaWNvcyBkZSBsaW5lYXMgcHVlZGVzIGlyIFthcXXDrV0oaHR0cHM6Ly9taWNoYWVsdG90aC5tZS9hLWRldGFpbGVkLWd1aWRlLXRvLXBsb3R0aW5nLWxpbmUtZ3JhcGhzLWluLXItdXNpbmctZ2dwbG90LWdlb21fbGluZS5odG1sKS4KCgojIyMjIyBVbiBwb2NvIHNvYnJlIGRhdG9zIHRlbXBvcmFsZXMKClBhcmEgdHJhYmFqYXIgY29uIGRhdG9zIHRlbXBvcmFsZXMsIFIgdGllbmUgZGlzdGludG9zIHBhcXVldGVzIHkgZXN0cnVjdHVyYXMsIHBlcm8gcGFyYSBtYW5pcHVsYXIgZmVjaGFzIHkgIGRhdG9zIHRlbXBvcmFsZXMgZW4gZWwgdGlkeXZlcnNlIHRlbmVtb3MgZWwgcGFxdWV0ZSBbYGx1YnJpZGF0ZWBdKGh0dHBzOi8vbHVicmlkYXRlLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2x1YnJpZGF0ZS1wYWNrYWdlLmh0bWwpLiBQYXJhIHVuYSBpbnRyb2R1Y2Npw7NuIGEgZmVjaGFzIHkgdGllbXBvIGVuIFIgW2FxdcOtXShodHRwczovL2RlcmVrc29uZGVyZWdnZXIuZ2l0aHViLmlvLzU3MEwvMTQtZGF0ZXMtYW5kLXRpbWVzLmh0bWwpLgoKCkVuIGdlbmVyYWwsIHBhcmEgcmVhbGl6YXIgYW7DoWxpc2lzIGVzdGFkw61zdGljb3MgZW4gUiBjb24gc2VyaWVzIGRlIHRpZW1wbyBzZSBuZWNlc2l0YSBxdWUgbG9zIGRhdG9zIGVzdMOpbiBlbiBtYXRyaWNlcywgcGVybyBub3NvdHJvcyBlbiBlbCBjdXJzbyBlc3RhbW9zIHRyYWJhamFuZG8gY29uIGVsIHRpZHl2ZXJzZSwgeSBsYSBsYSBlc3RydWN0dXJhIGRlIGRhdG9zIHVzYWRhIHNvbiBsb3MgZGF0YWZyYW1lcyBvIHRpYmJsZXMsIGVzdG8gZXJhIHVuIHByb2JsZW1hLCBwZXJvIHJlY2llbnRlbWVudGUsIGVsIHBhcXVldGUgW2B0c2liYmxlYF0oaHR0cHM6Ly90c2liYmxlLnRpZHl2ZXJ0cy5vcmcvKSBoYSBleHRlbmRpZG8gZWwgdGlkeXZlcnNlIHkgbGFzIHRpYmJsZXMgYSBsb3MgZGF0b3MgdGVtcG9yYWxlcywgY3JlYW5kbyB1bmEgbnVldmEgZXN0cnVjdHVyYSBkZSBkYXRvczogbGFzICJ0c2liYmxlcyIuIAoKQXBvecOhbmRvc2UgZW4gZXN0YSBudWV2YSBlc3RydWN0dXJhIGRlIGRhdG9zLCBsYXMgInRzaWJibGVzIiwgZWwgcGFxdWV0ZSBbYGZlYXN0YF0oaHR0cDovL2ZlYXN0cy50aWR5dmVydHMub3JnLykgcHJvcG9yY2lvbmEgbGFzIGhlcnJhbWllbnRhcyB5IGZ1bmNpb25lcyBuZWNlc2FyaWFzIHBhcmEgdHJhYmFqYXIgY29uIHNlcmllcyB0ZW1wb3JhbGVzIGVuIHVuIGVudG9ybm8gdGlkeS4gYGZlYXN0YCBlcyB1biBhY3LDs25pbW8gZGUgRmVhdHVyZSBFeHRyYWN0aW9uIEFuZCBTdGF0aXN0aWNzIGZvciBUaW1lIFNlcmllcy4gUGFyYSB1bmEgaW50cm9kdWNjacOzbiBhIGBmZWFzdGAgcHVlZGVzIGlyIFthcXXDrV0oaHR0cHM6Ly9ibG9nLm1pdGNoZWxsb2hhcmF3aWxkLmNvbS9ibG9nL2ZlYXN0cy8pIG8gW2FxdcOtXShodHRwczovL3JvYmpoeW5kbWFuLmNvbS9oeW5kc2lnaHQvZmVhc3RzLykuIAoKQXNpbWlzbW8sIGVsIHBhcXVldGUgW2BmYWJsZWBdKGh0dHA6Ly9mYWJsZS50aWR5dmVydHMub3JnLykgdGFtYmnDqW4gdXRpbGl6YSAidHNpYmJsZXMiIHkgcHJvcG9yY2lvbmEgIGhlcnJhbWllbnRhcyBwYXJhIGxhIHByZWRpY2Npw7NuIGRlIHNlcmllcyB0ZW1wb3JhbGVzLCBpbmNsdXllbmRvIG1vZGVsb3MgQVJJTUEgZW4gZWwgZW50b3JubyB0aWR5dmVyc2UuIFBhcmEgdW5hIGludHJvZHVjY2nDs24gYSBgZmFibGVgIHB1ZWRlcyBpciBbYXF1w61dKGh0dHBzOi8vYmxvZy5taXRjaGVsbG9oYXJhd2lsZC5jb20vYmxvZy9mYWJsZS8pLgoKW0FxdcOtXShodHRwczovL3Jlc291cmNlcy5yc3R1ZGlvLmNvbS9yc3R1ZGlvLWNvbmYtMjAxOS9tZWx0LXRoZS1jbG9jay10aWR5LXRpbWUtc2VyaWVzLWFuYWx5c2lzKSBwdWVkZXMgdmVyIHVuYSBjb25mZXJlbmNpYSBlbiBsYSBxdWUgc2UgZXhwbGljYSBsYXMgcHJpbmNpcGFsZXMgaWRlYXMgZGUgZXN0YSBudWV2YSBmb3JtYSBkZSB0cmFiYWphciBjb24gc2VyaWVzIHRlbXBvcmFsZXMgZW4gZWwgdGlkeXZlcnNlIAoKPGJyPgoKCiMjIDcuIE3DoXMgZGV0YWxsZXMvY29zYXMKCgojIyMgNy4xIGdlb21fc21vb3RoKCkKCllhIGhlbW9zIHVzYWRvIGBnZW9tX3Ntb290aCgpYC4gVW4gYXJndW1lbnRvIGltcG9ydGFudGUgZGUgYGdlb21fc21vb3RoKClgIGVzIGBtZXRob2RgIGNvbiBlbCBxdWUgc2Ugc2VsZWNjaW9uYSBlbCBtw6l0b2RvICBjb24gZWwgcXVlIHNlIG9idGllbmUgbGEgc21vb3RoIGN1cnZlLiBFbCBtw6l0b2RvIHBvciBkZWZlY3RvIGVzIGBtZXRob2QgPSAibG9lc3MiYC4gUHVlZGVzIGNvbnRyb2xhciBlbCBuaXZlbCBkZSBzbW9vdGhpbmcgY29uIGVsIHBhcmFtZXRybyAic3BhbiIsIHF1ZSB2YSBkZSAwICB0byAxIChtYXlvciBzdWF2aXphZG8pLgoKT3RyYXMgb3BjaW9uZXMgcGFyYSBnZW9tX3Ntb290aCgpOgoKLSBtZXRob2QgPSAibG0iIGZpdHMgYSBsaW5lYXIgbW9kZWwsIGdpdmluZyB0aGUgbGluZSBvZiBiZXN0IGZpdC4gCgotIG1ldGhvZCA9ICJybG0iIHdvcmtzIGxpa2UgbG0oKSwgYnV0IHVzZXMgYSByb2J1c3QgZml0dGluZyBhbGdvcml0aG0gc28gdGhhdCBvdXRsaWVycyBkb27igJl0IGFmZmVjdCB0aGUgZml0IGFzIG11Y2guIEl04oCZcyBwYXJ0IG9mIHRoZSBNQVNTIHBhY2thZ2UsIHNvIHJlbWVtYmVyIHRvIGxvYWQgdGhhdCBmaXJzdC4gCgotIHN0YXRfc21vb3RoKHNlID0gRkFMU0UpICwgaW5kaWNhIHNpIHNlIHJlcHJlc2VudGFuIGludGVydmFsb3MgZGUgY29uZmlhbnphIHBhcmEgbGEgbGluZWEgc3Vhdml6YWRhLgoKQWRlbcOhcyBkZSBsb3MgbcOpdG9kb3MgaW1wbGVtZW50YWRvcywgcG9kZW1vcyBlbGVnaXIgbnVlc3RybyBwcm9waW8gbcOpdG9kbywgeWEgc2VhIHVzYW5kbyBlbCBhcmd1bWVudG8gImZvcm11bGEiIG8gZGVmaW5pZW5kbyBudWVzdHJvIHByb3BpbyBtw6l0b2RvIGRlIGFsaXNhZG8gY29tbyBub3MgY3VlbnRhbiBbYXF1w61dKGh0dHBzOi8vZWxpb2NhbXAuZ2l0aHViLmlvL2NvZGlnby1yLzIwMTgvMDYvdHUtcHJvcGlvLWdlb20tc21vb3RoLykgCgoKYGBge3J9CnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgCiAgICAgZ2VvbV9wb2ludCgpIApwICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiBwb2x5KHgsNCkpCmBgYAoKClBvZGVtb3MsIG9idmlhbWVudGUsIGNvbXBhcmFyIGRvcyBtw6l0b2RvcyBkZSBhbGlzYWRvLiBQYXJhIHBvbmVyIG5vbWJyZSBhIGxvcyBkaWZlcmVudGVzIG3DqXRvZG9zIHNlIHB1ZWRlIGhhY2VyIGxvIHNpZ3VpZW50ZTogCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCkpICsgCiAgICAgZ2VvbV9wb2ludCgpCnAgKyBnZW9tX3Ntb290aChhZXMoY29sb3IgPSAibG9lc3MiKSAsIG1ldGhvZCA9ICJsb2VzcyIsIHNlID0gRkFMU0UpICsgCiAgICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSAibG0iKSAgICAsIG1ldGhvZCA9ICJsbSIgICAsIHNlID0gRkFMU0UpCmBgYAoKCjxicj4KCiMjIyA3LjIgw4FyZWFzIGJham8gbGEgY3VydmEKClBhcmEgdW4gcHJvZmVzb3IgZGUgZXN0YWTDrXN0aWNhL2Vjb25vbWV0csOtYSBlcyBpbXBvcnRhbnRlIHNhYmVyIGhhY2VyIGdyw6FmaWNvcyBjb21vIGVzdG9zOgoKbGlicmFyeSh0aWR5dmVyc2UpCmdncGxvdCh4eCwgYWVzKHgpKSArCiAgICBnZW9tX2Z1bmN0aW9uKGZ1biA9IGZ1bmN0aW9uKHgpIHt4KiozfSAsICBjb2xvdXIgPSAicmVkIikKCmdncGxvdCh4eCwgYWVzKHgpKSArCiAgICBnZW9tX2Z1bmN0aW9uKGZ1biA9IH4gLngqKjMgLCAgY29sb3VyID0gInJlZCIpCgoKCmBgYHtyLCBldmFsID0gRkFMU0V9Cnh4IDwtIGRhdGEuZnJhbWUoeCA9IGMoLTIsIDIpKQoKZ2dwbG90KHh4LCBhZXMoeCkpICsgZ2VvbV9mdW5jdGlvbihmdW4gPSBmdW5jdGlvbih4KSB7eCoqM30gLCAgY29sb3VyID0gInJlZCIpCgpnZ3Bsb3QoeHgsIGFlcyh4KSkgKyBnZW9tX2Z1bmN0aW9uKGZ1biA9IH4gLngqKjMgLCAgY29sb3VyID0gInJlZCIpCgpnZ3Bsb3QoeHgsIGFlcyh4KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1biA9IGZ1bmN0aW9uKHgpIHsgeCoqMyB9LCBnZW9tID0gImxpbmUiKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNDUlIn0KeHggPC0gZGF0YS5mcmFtZSh4ID0gYygtMiwgMikpCmdncGxvdCh4eCwgYWVzKHgpKSArIGdlb21fZnVuY3Rpb24oZnVuID0gZnVuY3Rpb24oeCkge3gqKjN9ICwgIGNvbG91ciA9ICJyZWQiKQpgYGAKCmBgYHtyLCBldmFsID0gVFJVRSwgb3V0LndpZHRoID0gIjQ1JSJ9CmdncGxvdCh4eCwgYWVzKHgpKSArIGdlb21fZnVuY3Rpb24oZnVuID0gZG5vcm0pICAKYGBgCgpgYGB7ciwgZXZhbCA9IFRSVUUsIG91dC53aWR0aCA9ICI0NSUifQpnZ3Bsb3QoeHgsIGFlcyh4ID0geCkpKwogIGdlb21fZnVuY3Rpb24oZnVuID0gZG5vcm0sIHhsaW0gPSBjKC00LCAwKSkgKwogIHN0YXRfZnVuY3Rpb24oZnVuID0gZG5vcm0sIGdlb20gPSAiYXJlYSIsIGZpbGwgPSAic3RlZWxibHVlIiwgeGxpbSA9IGMoMCwgNCkpICsKICB4bGltKC01LCA1KQpgYGAKCkxvIGFwcmVuZMOtIFthcXXDrV0oaHR0cHM6Ly9jaHJpc3RpYW5idXJraGFydC5kZS9ibG9nL2FyZWFfdW5kZXJfdGhlX2N1cnZlLykuCgpJbnNwaXJhZG8gcG9yIEZyYW4gTW9yaWxsYXMsIG90cm8gZWplbXBsbzoKCmBgYHtyLCAsIG91dC53aWR0aCA9ICI1MCUifQpubiA8LSAzMDAgOyBlc3BlcmFuemEgPC0gMTA7IHNkIDwtIDYKZGF0b3MgPC0gZGF0YS5mcmFtZShub3JtYWwgPSBybm9ybShubiwgZXNwZXJhbnphLCBzZCkpCgpnZ3Bsb3QoZGF0b3MsIGFlcyhub3JtYWwpKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSkpLCBiaW5zID0gMzApICsKICAgIGdlb21fZGVuc2l0eShjb2xvciA9ICJibHVlIikgKwogICAgZ2VvbV9mdW5jdGlvbihmdW4gPSBkbm9ybSwgYXJncyA9IGxpc3QoZXNwZXJhbnphLCBzZCksIGNvbG91ciA9ICJyZWQiKQpgYGAKCgpBdW5xdWUsIGxhIHZlcmRhZCwgaG95IGVuIGRpYSBoYXkgcGFxdWV0ZXMgZW4gUiBwYXJhIHRvZG8uIFBvciBlamVtcGxvIGVsIHBhcXVldGUgW2BuaHN0cGxvdGBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9uaHN0cGxvdC92aWduZXR0ZXMvbmhzdHBsb3QuaHRtbCkKCgpgYGB7ciwgb3V0LndpZHRoID0gIjUwJSJ9CmxpYnJhcnkobmhzdHBsb3QpCnBsb3R0dGVzdCh0ID0gMS45NiwgZGYgPSAxMDAwICwgdGFpbHMgPSAib25lIikKYGBgCgoKPGJyPgoKCiMjIyA3LjMgTGFiZWxsaW5nIGxhcyBvYnNlcnZhY2lvbmVzIGNvbiBnZ3JlcGVsCgpFbCBncsOhZmljbyBkZSBhYmFqbyBzZSBiYXNhIGVuIFtlc3RlIHBvc3RdKGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy9sdWJyaWRhdGUtbG9uZG9uLXN0YWdlLykgZGUgSnVsaWEgU2lsZ2UuIFV0aWxpemEgZWwgcGFxdWV0ZSBbYGdncmVwZWxdKGh0dHBzOi8vZ2l0aHViLmNvbS9zbG93a293L2dncmVwZWwpIHBhcmEgcG9kZXIgdmVyIGEgcXVpZW4gcGVydGVuZWNlIGxhIG9ic2VydmFjacOzbiBlbiB1biBncsOhZmljbyBkZSBwdW50b3MuCgpgYGB7cn0KbGlicmFyeShnZ3JlcGVsKQpkZiA8LSBnYXBtaW5kZXI6OmdhcG1pbmRlciAlPiUgZmlsdGVyKHllYXIgPT0gIjIwMDciKSAgJT4lIGZpbHRlcihjb250aW5lbnQgPT0gIkV1cm9wZSIpCmdncGxvdChkZiwgYWVzKGdkcFBlcmNhcCwgbGlmZUV4cCwgbGFiZWwgPSBjb3VudHJ5KSkgKyBnZW9tX3BvaW50KCkgKwogICAgIGxhYnModGl0bGUgPSAiR3LDoWZpY28gMTogRXNwZXJhbnphIGRlIHZpZGEgZnJlbnRlIGEgUElCIHBlciBjw6FwaXRhIiAsCiAgICAgICBjYXB0aW9uID0gIkRhdG9zIHByb3ZlbmllbnRlcyBkZSBnYXBtaW5kZXIiLAogICAgICAgeSA9ICJsaWZlRXhwIiwKICAgICAgIHggPSAiZ2RwUGVyY2FwIikgKyBnZW9tX3Ntb290aCgpICsKICAgICAgICBnZW9tX2xhYmVsX3JlcGVsKCkgCmBgYAoKCgo8YnI+CgojIyMgNy40IGBHR2FsbHlgIHBhY2thZ2UKCkhheSBtdWNob3MgcGFxdWV0ZXMgcXVlIHByb3BvcmNpb25hbiBmb3JtYXMgcsOhcGlkYXMgZGUgaGFjZXIgZ3LDoWZpY29zIGNvbW8gcG9yIGVqZW1wbG8gZXN0ZSBkZWwgcGFxdWV0ZSBbYEdHYWxseWBdKGh0dHBzOi8vZ2dvYmkuZ2l0aHViLmlvL2dnYWxseS9pbmRleC5odG1sKToKCmBgYHtyfQpsaWJyYXJ5KEdHYWxseSkKZ2dwYWlycyhpcmlzKQpnZ3BhaXJzKGlyaXMgJT4lIHNlbGVjdCgxOjQpICU+JSBuYS5vbWl0KCksIHByb2dyZXNzID0gRkFMU0UsIGxvd2VyID0gbGlzdChjb21ibyA9IHdyYXAoImZhY2V0aGlzdCIsIGJpbnM9NikpKQpgYGAKCjxicj4KCgojIyMgNy41IFVuIHBvY28gbcOhcyBkZSBhbm90YWNpb25lcyAKClBhcmEgcXVlIHVuIGdyw6FmaWNvIHNlYSBlZmVjdGl2byB5IG5vcyBoYWdhIHZlciBhbGfDum4gaGVjaG8gbyBjYXJhY3RlcsOtc3RpY2EgZGUgbG9zIGRhdG9zLCBtdWNoYXMgdmVjZXMgZXMgcHJlY2lzbyBoYWNlciBhbm90YWNpb25lcyBlbiBlbCBncsOhZmljbyBwYXJhIHNlw7FhbGFyIG8gcmVzYWx0YXIgY2llcnRvcyBhc3BlY3RvcyBkZSBlc3RlLiBFbiBlc3RhIHN1YnNlY2Npw7NuIHByZXNlbnRvIGFsZ8O6biBlamVtcGxvIGRlIHVzbyBkZSBsYXMgYW5vdGFjaW9uZXMgZW4gZ3LDoWZpY29zIGdncGxvdC4KCgotIEVuIHByaW1lciBsdWdhciB1biBlamVtcGxvIGRlIEhhZGxleS4gSGFkbGV5IG5vcyBkaWNlIHF1ZSBubyBoYXkgbmFkYSBub3ZlZG9zbyBleGNlcHRvIGVsIHVzbyBkZSAtSW5mIGFuZCBJbmYgY29tbyBwb3NpY2lvbmVzIHBhcmEgcmVmZXJpcnNlIGEgbG9zIGzDrW1pdGVzIGRlbCBncsOhZmljbywgdGhlIHRvcCBhbmQgYm90dG9tIChvciBsZWZ0IGFuZCByaWdodCkgbGltaXRzIG9mIHRoZSBwbG90LgoKYGBge3J9CnByZXNpZGVudGlhbCA8LSBzdWJzZXQocHJlc2lkZW50aWFsLCBzdGFydCA+IGVjb25vbWljcyRkYXRlWzFdKQoKcCA8LSBnZ3Bsb3QoZWNvbm9taWNzKSArICBnZW9tX2xpbmUoYWVzKGRhdGUsIHVuZW1wbG95KSkgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJibHVlIiwgInJlZCIpKSArCiAgeGxhYigiZGF0ZSIpICsgCiAgeWxhYigidW5lbXBsb3ltZW50IikKCiMtIGNvbWllbnphbiBsYXMgYW5vdGFjaW9uZXMgIApwICsgZ2VvbV9yZWN0KGFlcyh4bWluID0gc3RhcnQsIHhtYXggPSBlbmQsIGZpbGwgPSBwYXJ0eSksIAogICAgICAgICAgICAgIHltaW4gPSAtSW5mLCB5bWF4ID0gSW5mLCBhbHBoYSA9IDAuMiwgZGF0YSA9IHByZXNpZGVudGlhbCkgKyAKICAgIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBhcy5udW1lcmljKHN0YXJ0KSksIAogICAgICAgICAgICAgICBkYXRhID0gcHJlc2lkZW50aWFsLCBjb2xvdXIgPSAiZ3JleTUwIiwgYWxwaGEgPSAwLjUpICsgCiAgICBnZW9tX3RleHQoYWVzKHggPSBzdGFydCwgeSA9IDI1MDAsIGxhYmVsID0gbmFtZSksIAogICAgICAgICAgICAgICBkYXRhID0gcHJlc2lkZW50aWFsLCBzaXplID0gMywgdmp1c3QgPSAwLCBoanVzdCA9IDAsIG51ZGdlX3ggPSA1MCkgCmBgYAoKClVuYSBmb3JtYSBjb23Dum4gZGUgYW5vdGFjacOzbiBjb25zaXN0ZSBlbiBtYXJjYXIgbyBzdWJyYXlhciB1biBjb25qdW50byBkZSBwdW50b3MuIFBvciBlamVtcGxvIG1hcmNhciBsb3MgY29jaGVzIGRlIGxhIG1hcmNhIFN1YmFydSBlbiBlbCBzaWd1aWVudGUgZ3LDoWZpY28uIFNlIHB1ZWRlIGhhY2VyIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6IAoKCmBgYHtyfQpwIDwtIGdncGxvdChtcGcsIGFlcyhkaXNwbCwgaHd5KSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihtcGcsIG1hbnVmYWN0dXJlciA9PSAic3ViYXJ1IiksIGNvbG91ciA9ICJvcmFuZ2UiLCBzaXplID0gMykgKwogIGdlb21fcG9pbnQoKSAKcApgYGAKCgpDb21vIHZlcywgbG8gcXVlIHNlIGhhIGhlY2hvIGVzIHN1cGVycG9uZXIgdW5hIGNhcGEgY29uIHNvbG8gbGFzIG9ic2VydmFjaW9uZXMgZGUgU3ViYXJ1IHkgZ3JhZmljYXJsYXMgY29uIHVuIHB1bnRvIG3DoXMgZ3JhbmRlIGRlIGxvIGhhYml0dWFsIHkgY29uIHVuIGNvbG9yIGxsYW1hdGl2by4gRWwgcHJvYmxlbWEgZXMgcXVlIHNlIHZlIHF1ZSBzZWFuIGxhcyBvYnNlcnZhY2lvbmVzIGRlIFN1YmFydS4gRXN0byBzZSBwdWVkZSByZXNvbHZlciBkZSB2YXJpYXMgbWFuZXJhcyB1c2FuZG8gYW5ub3RhdGUoKToKCgpgYGB7cn0KcCArIGFubm90YXRlKGdlb20gPSAicG9pbnQiLCB4ID0gNS41LCB5ID0gNDAsIGNvbG91ciA9ICJvcmFuZ2UiLCBzaXplID0gMykgKyAKICAgIGFubm90YXRlKGdlb20gPSAicG9pbnQiLCB4ID0gNS41LCB5ID0gNDApICsgCiAgICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCB4ID0gNS42LCB5ID0gNDAsIGxhYmVsID0gInN1YmFydSIsIGhqdXN0ID0gImxlZnQiKQpgYGAKCk8gZGUgZXN0YSBvdHJhIGZvcm1hOgoKYGBge3J9CnAgKyBhbm5vdGF0ZShnZW9tID0gImN1cnZlIiwgeCA9IDQsIHkgPSAzNSwgeGVuZCA9IDIuNjUsIHllbmQgPSAyNywgCiAgICAgICAgICAgICAgY3VydmF0dXJlID0gLjMsIGFycm93ID0gYXJyb3cobGVuZ3RoID0gdW5pdCgyLCAibW0iKSkpICsKICAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgeCA9IDQuMSwgeSA9IDM1LCBsYWJlbCA9ICJzdWJhcnUiLCBoanVzdCA9ICJsZWZ0IikKYGBgCgoKRWwgcGFxdWV0ZSBbYGdnZm9yY2VgXShodHRwczovL2dnZm9yY2UuZGF0YS1pbWFnaW5pc3QuY29tLykgZXMgdW5hIGV4dGVuc2nDs24gYSBnZ3Bsb3QyIHF1ZSBwdWVkZSBzZXJ2aXIgcGFyYSBtdWNoYXMgY29zYXMsIGVudHJlIGVsbGFzIGhhY2VyIGFub3RhY2lvbmVzIG8gbWFyY2FzIGVuIGdyw6FmaWNvcyBnZ3Bsb3QuIFBvciBlamVtcGxvOgoKYGBge3J9CmdncGxvdChpcmlzLCBhZXMoU2VwYWwuTGVuZ3RoLCBQZXRhbC5MZW5ndGgpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBTcGVjaWVzKSkgKyAKICBnZ2ZvcmNlOjpnZW9tX21hcmtfZWxsaXBzZShhZXMobGFiZWwgPSBTcGVjaWVzLCBncm91cCA9IFNwZWNpZXMpKQpgYGAKCgpDb24gYGdnZm9yY2VgIHBvZGVtb3MgaGFzdGEgc2ltdWxhciB1biB6b29tOgoKCmBgYHtyfQpnZ3Bsb3QoaXJpcywgYWVzKFBldGFsLkxlbmd0aCwgUGV0YWwuV2lkdGgsIGNvbG91ciA9IFNwZWNpZXMpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZ2ZvcmNlOjpmYWNldF96b29tKHggPSBTcGVjaWVzID09ICJ2ZXJzaWNvbG9yIikKYGBgCgo8YnI+CgpFbCBwYXF1ZXRlL2V4dGVuc2nDs24gYGdnZm9yY2VgIGVzIHByZXR0eSBhd2Vzb21lLiBFbiBbZXN0ZSBwb3N0XShodHRwczovL3J2aWV3cy5yc3R1ZGlvLmNvbS8yMDE5LzA5LzE5L2ludHJvLXRvLWdnZm9yY2UpIHB1ZWRlIHZlcmxvIGVuIGFjY2nDs24gaGFjaWVuZG8gbWFwYXMuCgo8YnI+CgpPdHJvIGVuZm9xdWUgcGFyYSBoYWNlciBhbm90YWNpb25lcywgYnVlbm8sIGVuIHJlYWxpZGFkIGNlbnRyYXNlIGVuIHVuIGdydXBvIGRlIG9ic2VydmFjaW9uZXMgZXMgdXRpbGl6YXIgZWwgcGFxdWV0ZSBbYGdnaGlnaGxpZ2h0YF0oaHR0cHM6Ly9naXRodWIuY29tL3l1dGFubmloaWxhdGlvbi9nZ2hpZ2hsaWdodCk6CgoKYGBge3J9CmRmIDwtIGdhcG1pbmRlcjo6Z2FwbWluZGVyICU+JSBmaWx0ZXIoY29udGluZW50ID09ICJFdXJvcGUiKQpnZ3Bsb3QoZGYsIGFlcyh5ZWFyLCBsaWZlRXhwLCBncm91cCA9IGNvdW50cnkpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2doaWdobGlnaHQ6OmdnaGlnaGxpZ2h0KGNvdW50cnkgJWluJSBjKCJTcGFpbiIsICJQb3J0dWdhbCIpKQpgYGAKCgpgYGB7cn0KZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBhcy5mYWN0b3IoU3BlY2llcykpKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2doaWdobGlnaHQ6OmdnaGlnaGxpZ2h0KCkgKyAKICBmYWNldF93cmFwKHZhcnMoU3BlY2llcykpCmBgYAoKCgoKCjxicj4KCgojIyA4LiBBc2lzdGVudGVzIHBhcmEgZ2dwbG90MgoKQWhvcmEgbWlzbW8gZXhpc3RlbiAyIGFzaXN0ZW50ZXMgcGFyYSBjcmVhciBncsOhZmljb3MgZ2dwbG90IGEgdHJhdsOpcyBkZSBpbnRlcmZhY2VzIGdyw6FmaWNhczogW2BnZ1RoZW1lQXNzaXN0YF0oaHR0cHM6Ly9naXRodWIuY29tL2NhbGxpZ3Jvc3MvZ2d0aGVtZWFzc2lzdCkgeSBbYGVzcXVpc3NlYF0oaHR0cHM6Ly9kcmVhbXJzLmdpdGh1Yi5pby9lc3F1aXNzZS9pbmRleC5odG1sKSAKCi0gRWwgcGFxdWV0ZSBbYGdnVGhlbWVBc3Npc3RgXShodHRwczovL2dpdGh1Yi5jb20vY2FsbGlncm9zcy9nZ3RoZW1lYXNzaXN0KSBmYWNpbGl0YSBtZWRpYW50ZSB1biBhZGRpbmcgZGUgUlN0dWRpbyBsYSBlZGljacOzbiBkZSBsb3MgZGV0YWxsZXMgZGUgdW4gZ3LDoWZpY287IGVzIGRlY2lyLCBwdWVkZXMgY29tZW56YXIgaGFjaWVuZG8gdW4gZ3LDoWZpY28gYsOhc2ljbyBlbiBSU3R1ZGlvLCBwYXJhIGRlc3B1w6lzIGFicmlyIGxhIGludGVyZmF6IGRlIGBnZ1RoZW1lQXNzaXN0YCBwYXJhIG1vZGlmaWNhciBjb24gZWwgYXNpc3RlbnRlIHZpc3VhbCB0b2RvcyBsb3MgZWxlbWVudG9zIGVzdMOpdGljb3MgZGUgZ3LDoWZpY28gY29tbyB0w610dWxvcywgbGV5ZW5kYXMsIGNvbG9yZXMgZXRjLi4uIGV0Yy4uLgoKICBQYXJhIGVsbG8sIHByaW1lcm8gaGFzIGRlIGluc3RhbGFyIGVsIHBhcXVldGUgY29uIGBpbnN0YWxsLnBhY2thZ2VzKCJnZ1RoZW1lQXNzaXN0IilgLCBkZXNwdcOpcyBjYXJnYXJsbyBjb24gYGxpYnJhcnkoZ2dUaGVtZUFzc2lzdClgLiBVbmEgdmV6IGhhcyBjYXJnYWRvIGVsIHBhcXVldGUgZW4gbWVtb3JpYSBoYXMgZGUgc2VsZWNjaW9uYXIgY29uIGVsIGN1cnNvciBlbCBjw7NkaWdvIHF1ZSBnZW5lcmEgZWwgZ2dwbG90IHF1ZSBxdWllcmVzIG1vZGlmaWNhci90dW5lYXIuIFVuYSB2ZXogdGllbmVzIG1hcmNhZG8gY29uIGVsIHJhdMOzbiBlbCBjw7NkaWdvIGRlbCBncsOhZmljbywgaGFzIGRlIHNlZ3VpciwgZW4gUlN0dWRpbywgZXN0YSBydXRhIGRlIG1lbsO6czogYFRvb2xzID4gQWRkaW5ncyA+IEJyb3dzZSBBZGRpbmdzIC4uLmAsIHBhcmEgc2VsZWNjaW9uYXIgZmluYWxtZW50ZSBlbCBhZGRpbmcgbGxhbWFkbyAiZ2dwbG90IFRoZW1lIEFzaXN0YW50Ii4KCiAgU2UgYWJyaXLDoSB1biBpbnRlcmZheiBkb25kZSBwb2Ryw6FzIG1vZGlmaWNhciBsYSBtYXlvcsOtYSBkZSBlbGVtZW50b3MgZGVsIGdyw6FmaWNvLiBDdWFuZG8gaGF5YXMgZGVqYWRvIGVsIGdyw6FmaWNvIGEgdHUgZ3VzdG8gcGluY2hhcyBlbiBgRE9ORWAgeSB0ZSBkZXZvbHZlcsOhIGVsIGPDs2RpZ28gcXVlIHJlcHJvZHVjZSBlbCBncsOhZmljbyB0YWwgeSBjb21vIGxvIGVsZWdpc3RlIGVuIGVsIGludGVyZmF6LgoKICBQb2TDqWlzIHZlciB1biBlamVtcGxvIGVuIDxodHRwczovL2dpdGh1Yi5jb20vY2FsbGlncm9zcy9nZ3RoZW1lYXNzaXN0PgoKCjxicj4KCi0gRWwgcGFxdWV0ZSBbYGVzcXVpc3NlYF0oaHR0cHM6Ly9kcmVhbXJzLmdpdGh1Yi5pby9lc3F1aXNzZS9pbmRleC5odG1sKSBwZXJtaXRlIGNyZWFyIGdyw6FmaWNvcyBnZ3Bsb3QgZGVzZGUgY2VybyBjb24gdW5hIGludGVyZmF6IGdyw6FmaWNhLgoKICBQYXJhIHVzYXJsbyB0aWVuZXMgcXVlIGluc3RhbGFybG8gY29uIGBpbnN0YWxsLnBhY2thZ2VzKCJlc3F1aXNzZSIpYCBwYXJhIGx1ZWdvIGFicmlybG8gc2lndWllbmRvIGVzdGEgcnV0YSBkZSBtZW7DunMgZW4gUlN0dWRpbzogYFRvb2xzID4gQWRkaW5ncyA+IEJyb3dzZSBBZGRpbmdzIC4uLmAgcGFyYSBlbGVnaXIgZWwgYWRkaW5nIGNvbiBub21icmU6ICJlc3F1aXNzZSAtIGdncGxvdCBidWlsZGVyIi4KCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZmlnLmFzcCA9IDcvOX0KdHdlZXRybWQ6OnR3ZWV0X2VtYmVkKCJodHRwczovL3R3aXR0ZXIuY29tL1dlQXJlUkxhZGllcy9zdGF0dXMvMTEzOTU5MTc2MzQzMjIyMjcyMyIsIHRoZW1lID0gImxpZ2h0IiwgYWxpZ24gPSAiY2VudGVyIiwgbWF4d2lkdGggPSA0MDApCmBgYAoKCjxicj4KCiMjIDkuIEdyw6FmaWNvcyBpbnRlcmFjdGl2b3MKCkxvcyBncsOhZmljb3MgaW50ZXJhY3Rpdm9zLCBjb21vIHN1IG5vbWJyZSBpbmRpY2EsIHBlcm1pdGUgYWwgdXN1YXJpbyBpbnRlcmFjdHVhciBjb24gZWwgZ3LDoWZpY28sIGFicmnDqW5kb3NlIHBvc2liaWxpZGFkZXMgY29tbyBjZW50cmFyc2UgZW4gcGFydGUgZGVsIGdyw6FmaWNvICh6b29taW5nKSwgaGlnaGxpZ2h0aW5nLCBvIG1vc3RyYXIgaW5mb3JtYWNpw7NuIGFkaWNpb25hbCBhbCBwaW5jaGFyIGVuIGFsZ8O6biBlbGVtZW50byBkZWwgZ3LDoWZpY28sIGV0YyAuLi4KCkVuIGdlbmVyYWwsIEphdmFTY3JpcHQgKEpTKSBlcyBlbCBsZW5ndWFqZSB1dGlsaXphZG8gcGFyYSBoYWNlciBncsOhZmljb3MgaW50ZXJhY3Rpdm9zIGNvbiBsaWJyZXLDrWFzIGNvbW8gRDMsIENoYXJ0LCBQbG90bHksIFZpcyBIaWdoY2hhcnRzLCAuLi4KClJlY2llbnRlbWVudGUsIGVsIHBhcXVldGUgZGUgUiBbYGh0bWx3aWRnZXRzYF0oaHR0cDovL3d3dy5odG1sd2lkZ2V0cy5vcmcvKSBoYSBmYWNpbGl0YWRvIGVsIHVzbyBkZSBsYXMgbGlicmVyw61hcyBkZSBKUyBlbiBSLiBBY3R1YWxtZW50ZSwgcGFxdWV0ZXMgZGUgUiwgY29tbyBsZWFmbGV0LCBEVCwgZHlncmFwaHMsIG5ldHdvcmtEMyB5IG11Y2hvcyBvdHJvcywgdXRpbGl6YW4gZWwgZnJhbWV3b3JrIHByb3B1ZXN0byBwb3IgaHRtbHdpZGdldHMgcGFyYSBoYWNlciBkaXNwb25pYmxlcyBsb3MgZ3LDoWZpY29zIGludGVyYWN0aXZvcyBkZSBKUyBlbiBSLgoKClBhcmEgZGFyb3MgY3VlbnRhIGRlIGxvIGbDoWNpbCBxdWUgZXMgaGFjZXIgdW4gZ3LDoWZpY28gaW50ZXJhY3Rpdm8gY29uIFIgdXNhcmVtb3MgZWwgcGFxdWV0ZSBbYHBsb3RseWBdKGh0dHBzOi8vcGxvdC5seS9yLykgcXVlIGhhY2UgcG9zaWJsZSB1c2FyIGxhIGxpYnJlcsOtYSBwbG90bHkuanMgZW4gUi4gQ29uIGBwbG90bHlgIHNlIHB1ZWRlbiBoYWNlciBtdWNob3MgdGlwb3MgZGUgZ3LDoWZpY29zLCBwZXJvIHBvciBlamVtcGxvLCBwZXJtaXRlIGNvbiB1bmEgc29sYSBsaW5lYSBjb252ZXJ0aXIgdW4gZ3LDoWZpY28gZ2dwbG90IGVuIGludGVyYWN0aXZvOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShwbG90bHkpCnAgPC0gZ2dwbG90KGlyaXMsIGFlcyhTZXBhbC5MZW5ndGgsIFBldGFsLkxlbmd0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyAgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoKQpnZ3Bsb3RseShwKQpgYGAKCltBcXXDrV0oaHR0cHM6Ly9wbG90bHktci5jb20vcHJlZmFjZS5odG1sKSBwdWVkZXMgZW5jb250cmFhciB1biBib29rZG93biBzb2JyZSBwbG90bHkuCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcDEgPC0gcCArIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoU3BlY2llcykpIApnZ3Bsb3RseShwMSkKYGBgCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcCA8LSBnZ3Bsb3QobXBnLCBhZXMoY2xhc3MpKSArICBnZW9tX2JhcihmaWxsID0gInN0ZWVsYmx1ZSIpICsgY29vcmRfZmxpcCgpCmdncGxvdGx5KHApCmBgYAoKCltBcXXDrV0oaHR0cHM6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS9pbnRlcmFjdGl2ZS1jaGFydHMuaHRtbCkgcHVlZGVzIHZlciB1biBnYWxlcsOtYSBkZSBlamVtcGxvcyBkZSBncsOhZmljb3MgaW50ZXJhY3Rpdm9zIGhlY2hvcyBjb24gUi4gW0FxdcOtXShodHRwczovL21vZGVybmRhdGEucGxvdC5seS9pbnRlcmFjdGl2ZS1yLXZpc3VhbGl6YXRpb25zLXdpdGgtZDMtZ2dwbG90Mi1yc3R1ZGlvLykgdW4gcG9zdCwgeWEgZGUgMjAxNSwgcGFyYSBpbmljaWFyc2UgdW4gcG9jbyBlbiBlc3RvcyB0ZW1hcy4gCgoKSGF5IG11Y2hvcyBwYXF1ZXRlcyBxdWUgcGVybWl0ZW4gaGFjZXIgZ3LDoWZpY29zIGludGVyYWN0aXZvcyBlbiBSLCBwb3IgZWplbXBsbyBbYGxlYWZsZXRgXShodHRwOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pLCBxdWUgcGVybWl0ZSBoYWNlciBtYXBhcyBpbnRlcmFjdGl2b3MgbXV5IGbDoWNpbG1lbnRlLgoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGxlYWZsZXQpCm0gPC0gbGVhZmxldCgpCm0gPC0gYWRkVGlsZXMobSkKbSA8LSBhZGRNYXJrZXJzKG0sIGxuZyA9IDE3NC43NjgsIGxhdCA9LTM2Ljg1MiwgcG9wdXAgPSAiVGhlIGJpcnRocGxhY2Ugb2YgUiIpCm0KYGBgCgoKVG9kb3MgZXN0b3MgcGFxdWV0ZXNeW0VuIGVsIG1vbWVudG8gZGUgZXNjcmliaXIgZXN0YXMgbm90YXMgZXJhbiAxMDcgcGFxdWV0ZXNdIHF1ZSBwZXJtaXRlbiBoYWNlciBncsOhZmljb3MgaW50ZXJhY3Rpdm9zIGEgdHJhdsOpcyBkZSBgaHRtbHdpZGdldHNgIHNlIHB1ZWRlbiBjb25zdWx0YXIgZW46IDxodHRwOi8vZ2FsbGVyeS5odG1sd2lkZ2V0cy5vcmcvPi4gRG9zIHBhcXVldGVzIHF1ZSBubyBlc3TDoW4gZW4gbGEgZ2FsbGVyeTogW2BBcGV4Y2hhcnRzYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2FwZXhjaGFydGVyL3ZpZ25ldHRlcy9zdGFydGluZy13aXRoLWFwZXhjaGFydHMuaHRtbCkgeSBbYFRTcGxvdGx5YF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1RTcGxvdGx5L3ZpZ25ldHRlcy9UU3Bsb3RseS5odG1sKS4KCgpFbiBbSW50ZXJhY3RpdmUgd2ViLWJhc2VkIGRhdGEgdmlzdWFsaXphdGlvbiB3aXRoIFIsIHBsb3RseSwgYW5kIHNoaW55XShodHRwczovL3Bsb3RseS1yLmNvbS8pIGV4cGxpY2FuIG3DoXMgZGV0YWxsYWRhbWVudGUgY29tbyBoYWNlciBncsOhZmljb3MgaW50ZXJhY3Rpdm9zIGVuIFIuIFRlbmRyw6kgcXVlIHJlbGVlciBzdSBzZWNjacOzbiBbU2F2aW5nIGFuZCBlbWJlZGRpbmcgSFRNTF0oaHR0cHM6Ly9wbG90bHktci5jb20vc2F2aW5nLmh0bWwpIHBhcmEgbW9zdHJhciBlbiBlbCB0dXRvcmlhbCBhbGd1bm8gZGUgbG9zIGdyw6FmaWNvcyBkaW7DoW1pY29zIHF1ZSBoZSBoZWNobywgcGVybyBhaG9yYSB0ZW5nbyBwcmlzYSwgbGEgcHLDs3hpbWEgY2xhc2UgZXMgcHJvbnRvIHkgZW1wZXphbW9zIHPDrSBvIHPDrSBnZ3Bsb3QyLgoKSGFkbGV5IHRhbWJpw6luIGVzdMOhIGVzY3JpYmllbmRvIHVuIGJvb2tkb3duIHNvYnJlIFNoaW55OiBbTWFzdGVyaW5nIFNoaW55XShodHRwczovL21hc3RlcmluZy1zaGlueS5vcmcvKS4gRW4gcGFsYWJyYXMgZGUgSGFkbGV5OiBTaGlueSBpcyBhIGZyYW1ld29yayBmb3IgY3JlYXRpbmcgd2ViIGFwcGxpY2F0aW9ucyB1c2luZyBSIGNvZGUuIFBhcmEgdmVyIHF1ZSBzaWduaWZpY2EgZXN0byBwdWVkZXMgdmVyIGxhIHNpZ3VpZW50ZSBbZ2FsZXLDrWEgY29uIGVqZW1wbG9zIGRlIGFwbGljYWNpb25lcyBzaGlueV0oaHR0cHM6Ly9zaGlueS5yc3R1ZGlvLmNvbS9nYWxsZXJ5LykuCgoKIyMjIGdnYW5pbWF0ZSAKCltgZ2dhbmltYXRlYF0oaHR0cHM6Ly9nZ2FuaW1hdGUuY29tLykgZXMgdW4gcGFxdWV0ZSBxdWUgbm8gaGFjZSBncsOhZmljb3MgZGluw6FtaWNvcywgcGVybyBwZXJtaXRlIGFuaW1hciBncsOhZmljb3MgbWVkaWFudGUgbGEgY3JlYWNpw7NuIGNyZWFjacOzbiBkZSBzZWN1ZW5jaWFzIGRlIGdyw6FmaWNvcy4gTWVqb3IgcXVlIGV4cGxpY2FybG8gdmlzaXRhIHN1IHNlY2Npw7NuIGRlIGVqZW1wbG9zIGVuIHN1IHdpa2k6IDxodHRwczovL2dpdGh1Yi5jb20vdGhvbWFzcDg1L2dnYW5pbWF0ZS93aWtpPi4gUG9yIGVqZW1wbG8gW2VzdGUgZWplbXBsb10oaHR0cHM6Ly9naXRodWIuY29tL3Rob21hc3A4NS9nZ2FuaW1hdGUvd2lraS9UcmFja2luZy1vZi1odXJyaWNhbmVzLWFuZC10eXBob29ucykgIG8gIFtlc3RlIGVqZW1wbG9dKGh0dHBzOi8vZ2l0aHViLmNvbS90aG9tYXNwODUvZ2dhbmltYXRlL3dpa2kvV29ybGQtQ3VwLUdvYWwtQW5pbWF0aW9uKSBkZSBqdXJnb2wuIEVuIFtlc3RlIHBvc3RdKGh0dHBzOi8vd3d3LmRhdGFub3ZpYS5jb20vZW4vYmxvZy9nZ2FuaW1hdGUtaG93LXRvLWNyZWF0ZS1wbG90cy13aXRoLWJlYXV0aWZ1bC1hbmltYXRpb24taW4tci8pIGV4cGxpY2FuIGNvbW8gdXNhcmxvLgoKClVuIGVqZW1wbG8gY29uIGxvcyBkYXRvcyBkZSBgZ2FwbWluZGVyYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2FwbWluZGVyKQpsaWJyYXJ5KGdnYW5pbWF0ZSkKZ2FwbWlkZXJfZXVyb3BlIDwtIGdhcG1pbmRlciAlPiUgZmlsdGVyKGNvbnRpbmVudCA9PSAiRXVyb3BlIikKZ2dwbG90KGdhcG1pZGVyX2V1cm9wZSwgYWVzKGdkcFBlcmNhcCwgbGlmZUV4cCwgc2l6ZSA9IHBvcCwgY29sb3VyID0gY291bnRyeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC43LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjb3VudHJ5X2NvbG9ycykgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDIsIDEyKSkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZmFjZXRfd3JhcCh+Y29udGluZW50KSArCiAgIyBIZXJlIGNvbWVzIHRoZSBnZ2FuaW1hdGUgc3BlY2lmaWMgYml0cwogIGxhYnModGl0bGUgPSAnWWVhcjoge2ZyYW1lX3RpbWV9JywgeCA9ICdHRFAgcGVyIGNhcGl0YScsIHkgPSAnbGlmZSBleHBlY3RhbmN5JykgKwogIHRyYW5zaXRpb25fdGltZSh5ZWFyKSArCiAgZWFzZV9hZXMoJ2xpbmVhcicpIApgYGAKCltBcXXDrV0oaHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20vdGhvbWFzcDg1LzA1MTY5YWQ0NGRkY2M4ZWQ1NmRhNmZmN2JmN2ZiZTM2KSB0aWVuZXMgdW4gZWplbXBsbyBwYXJhIGhhY2VybG8gcGFyYSBsb3MgY2luY28gY29udGluZW50ZXMuIAoKPGJyPgoKCgojIyBCaWJsaW8vZWplbXBsb3MvcmVjdXJzb3MKCgpTb24gcmVjdXJzb3MgcXVlIGhlIHZpc3RvIG8gdXRpbGl6YWRvIG1pZW50cmFzIGVzY3JpYsOtYSBlc3RhIG5vdGFzLCB5IG5vIHF1aWVybyBvbHZpZGFybG9zOgoKLSBbQm9va2Rvd24gb2ZpY2lhbCBzb2JyZSBnZ3Bsb3QyXShodHRwczovL2dncGxvdDItYm9vay5vcmcvKQoKLSBbV2ViaW5hciBkZSBUaG9tYXMgTC4gUGVkZXJzZW4gc29icmUgZ2dwbG90Ml0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1oMjlnMjF6MGE2OCZmZWF0dXJlPXlvdXR1LmJlKQoKLSBbRGF0YSBWaXN1YWxpemF0aW9uIHdpdGggUiBkZSBSLiBDYWJhY29mZl0oaHR0cHM6Ly9ya2FiYWNvZmYuZ2l0aHViLmlvL2RhdGF2aXMvaW5kZXguaHRtbCkuCgotIFtDw6lkcmljIFNjaGVyZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9aM3R0L1RpZHlUdWVzZGF5KTogdW5hIHNlcmllIGRlIHZpc3VhbGl6YWNpb25lcyBpbmNyZcOtYmxlcyBhc29jaWFkYXMgYWwgcHJveWVjdG8gdGlkeSBUdWVzZGF5LgoKLSBbZGF0YS10by12aXpdKGh0dHBzOi8vd3d3LmRhdGEtdG8tdml6LmNvbS8pOiB1bmEgcGxhbnRpbGxhIHF1ZSB0ZSBwdWVkZSBheXVkYXIgYSBkZWNpZGlyIHF1w6kgZ3LDoWZpY28gdXNhci4KCgotIFtyLXN0YXRpc3RpY3MuY29dKCBodHRwOi8vci1zdGF0aXN0aWNzLmNvL2dncGxvdDItVHV0b3JpYWwtV2l0aC1SLmh0bWwpOiB0dXRvcmlhbCBgZ2dwbG90MmAuCgoKLSBbQSBnZ3Bsb3QyIFR1dG9yaWFsIGZvciBCZWF1dGlmdWwgUGxvdHRpbmcgaW4gUl0oaHR0cHM6Ly9jZWRyaWNzY2hlcmVyLm5ldGxpZnkuY29tLzIwMTkvMDgvMDUvYS1nZ3Bsb3QyLXR1dG9yaWFsLWZvci1iZWF1dGlmdWwtcGxvdHRpbmctaW4tci8pOiB0dXRvcmlhbCBgZ2dwbG90MmAuCgotIFtUaGUgRXZvbHV0aW9uIG9mIGEgZ2dwbG90IChFcC4gMSldKGh0dHBzOi8vY2Vkcmljc2NoZXJlci5uZXRsaWZ5LmNvbS8yMDE5LzA1LzE3L3RoZS1ldm9sdXRpb24tb2YtYS1nZ3Bsb3QtZXAuLTEvKS4gVW4gcG9zdCBtdXkgZGlkw6FjdGljbyBkb25kZSBzZSB2YSB0cmFuc2Zvcm1hbmRvIHVuIGdyw6FmaWNvIGhhc3RhIGhhY2VybG8gbXV5IGNodWxvLgoKCi0gW0VEQVYgaW5mb10oaHR0cHM6Ly9lZGF2LmluZm8vYm94Lmh0bWwpLiBCb29rZG93biBhw7puIGVuIGNvbnN0cnVjY2nDs24gY29uIGJ1ZW5vcyBlamVtcGxvcy4KCgotIFtUb23igJlzIENvb2tib29rIGZvciBCZXR0ZXIgVml6XShodHRwczovL2p0aG9tYXNtb2NrLmdpdGh1Yi5pby9uZmxfcGxvdHRpbmdfY29va2Jvb2svI2xpY2Vuc2UpLiBVbiBidWVuIHR1dG9yaWFsIGNvbiBjb25zZWpvcyB5IGRlbcOhcyBzb2JyZSBncsOhZmljb3MgZ2dwbG90LiBQb3IgZWplbXBsbyBtaXJhIGxvcyAiVXNlZnVsIGNvZGUgY2h1bmtzIi4KCgotIFtnZ3Bsb3QyIFF1aWNrIFJlZmVyZW5jZTogZ2VvbV0oaHR0cDovL3NhcGUuaW5mLnVzaS5jaC9xdWljay1yZWZlcmVuY2UvZ2dwbG90Mi9nZW9tKS4gVW5hIGNoZWF0c2hlZXQgcGFyYSB2ZXIgbG9zIGdlb21zX3h4KCkgZGlzcG9uaWJsZXMKCi0gW0VtaSBUYW5ha2EgLSAxXShodHRwczovL2VtaXRhbmFrYS5vcmcvd29ya3Nob3BVVG9reW8yMDE4L2RheTEtc2Vzc2lvbjAyLWRhdGF2aXMuaHRtbCMxKS4gU29uIHVuYXMgdHJhbnBhcmVuY2lhcyBkZSBFbWkgVGFuYWthIGRvbmRlIGhhY2UgdW4gcmVwYXNvIGZhbnTDoXN0aWNvIGEgbGEgZ2dwbG90MiBncmFtbWFyLiBFbWkgVGFuYWthIGVzIGxvIG1lam9yIHBhcmEgY29ub2NlciBsYSB2ZXJkYWQgc29icmUgZ2dwbG90MiBlbiBwb2NvIHRpZW1wbzsgcG9yIGVqZW1wbG8gbGEgc2xpZGUgIzYuCgotIFtEYXRhIFZpc3VhbGl6YXRpb24gaW4gdGhlIFRpZHl2ZXJzZV0oaHR0cHM6Ly9hbGlzb24ubmV0bGlmeS5jb20vdW8tdGlkeS1iYWtlb2ZmLyMxKS4gT3RyYXMgZmFudMOhc3RpY2FzIHRyYW5zcGFyZW5jaWFzIHNvYnJlIGdncGxvdDIsIGVzdGEgdmV6IGRlIEFsaXNvbiBIaWxsLgoKCi0gW1ByZWd1bnRhcyBzb2JyZSBnZ3Bsb3QyIGVuIFN0YWNrb3ZlcmZsb3ddKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zL3RhZ2dlZC9nZ3Bsb3QyKS4gU2VndXJvcyBxdWUgZW5jdWVudHJhcyB0dSBwcmVndW50YSB5IGFsZ3VpZW4gbGEgaGEgcmVzcG9uZGlkbyEhCgoKLSBbRGF0YSBWaXN1YWxpemF0aW9uIHdpdGggZ2dwbG90Ml0oaHR0cHM6Ly9jZW5nZWwuZ2l0aHViLmlvL1ItZGF0YS12aXovZGF0YS12aXN1YWxpemF0aW9uLXdpdGgtZ2dwbG90Mi5odG1sKS4gVW4gYm9va2Rvd24gc29icmUgZ2dwbG90Mi4gTm8gbG8gaGUgbGXDrWRvIHBlcm8gdGllbmUgYnVlbmEgcGludGEgcGFyYSBpbmljaWFyc2UgY29uIGdncGxvdC4KCgotIFtwcmFjdGljYWxnZ10oaHR0cHM6Ly93aWxrZWxhYi5vcmcvcHJhY3RpY2FsZ2cvKS4gVW4gcGFxdWV0ZSBjb24gZWplbXBsb3MgZGUgNiB2aXN1YWxpemFjaW9uZXMgZXhwbGljYWRhcyBwYXNvIGEgcGFzby4KCgotIFt2aXotcHViXShodHRwczovL2J1ZmYubHkvMm56TVpQWCkuIFVuIHJlcG8gZGUgR2l0aHViIGNvbiBncsOhZmljb3MgeSB2aXN1YWxpemFjaW9uZXMsIG9idmlhbWVudGUgY29uIGVsIGPDs2RpZ28uCgoKCi0gW1RoZSBnZ3Bsb3QgZmxpcGJvb2tdKGh0dHBzOi8vZXZhbWFlcmV5LmdpdGh1Yi5pby9nZ3Bsb3RfZmxpcGJvb2svZ2dwbG90X2ZsaXBib29rX3hhcmluZ2FuLmh0bWwjMSkuIE90cmFzIHRyYW5zcGFyZW5jaWFzIHNvYnJlIGdncGxvdDIsIGRlIEdpbmEgUmV5bm9sZHMuCgoKLSBbQXJ0aXN0aWMgY29kaW5nIGZvciB0aGUgdXNlUl0oaHR0cHM6Ly93d3cud2lsbGlhbXJjaGFzZS5jb20vcG9zdC9hcnRpc3RpYy1jb2RpbmctZm9yLXRoZS11c2VyLTEyLW1vbnRocy1vZi1hcnQtanVuZS8pLiBVbiBwb3N0IGRlIFdpbGwgQ2hhc2UgcGFyYSBpbnRyb2R1Y2lyc2UgZW4gZWwgbXVuZG8gZGUgaGFjZXIgYVJ0ZSBjb24gUi4gCgoKLSBbWW91IGNhbiByZXBsaWNhdGUgYWxtb3N0IGFueSBwbG90IHdpdGggUl0oaHR0cHM6Ly9zaW1wbHlzdGF0aXN0aWNzLm9yZy8yMDE5LzA4LzI4L3lvdS1jYW4tcmVwbGljYXRlLWFsbW9zdC1hbnktcGxvdC13aXRoLWdncGxvdDIvKS4gSW1wcmVzaW9uYW50ZSBwb3N0IGRlIFJhZmFlbCBJcml6YXJyeSBkb25kZSByZXBsaWNhIGNvbiBSIDUgdmlzdWFsaXphY2lvbmVzLgoKLSBbRGF0YSwgbW92aWVzIGFuZCBnZ3Bsb3QyXShodHRwOi8vc21hcnRlcnBvbGFuZC5wbC9pbmRleC5waHAvMjAxOC8xMi9kYXRhLW1vdmllcy1hbmQtZ2dwbG90Mi8pLiBVbm9zIHBvc3RlcnMgaW5jcmXDrWJsZXMgaGVjaG9zIGNvbiBSLgoKLSBMYSByZXZpc3RhIFsqKkJ1enpmZWRkKipdKGh0dHBzOi8vd3d3LmJ1enpmZWVkbmV3cy5jb20vKSBtYW50aWVuZSB1bmEgc2VyaWUgZGUgW3JlcG9zIGVuIEdpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL0J1enpGZWVkTmV3cy8pIGNvbiBlbCBjw7NkaWdvIHkgZGF0b3MgcGFyYSByZXBsaWNhciBzdXMgYW7DoWxpc2lzLiBFbiBjb25jcmV0bywgZW4gW2VzdGUgcmVwb10oaHR0cHM6Ly9naXRodWIuY29tL0J1enpGZWVkTmV3cy9ldmVyeXRoaW5nKSBoYXkgdW4gbGlzdGFkbyBkZSBhbsOhbGlzaXMuIENvbW8gZWplbXBsbywgW2VzdGUgZ3LDoWZpY29dKGh0dHBzOi8vdHdpdHRlci5jb20vcGFsZGhvdXMvc3RhdHVzLzExOTAwMjI2NjQ4MDg3ODM4NzIvcGhvdG8vMSksIGFwYXJlY2UgZW4gW2VzdGUgYXJ0w61jdWxvXShodHRwczovL3d3dy5idXp6ZmVlZG5ld3MuY29tL2FydGljbGUvbGFtdm8vc29jaWFsLXNlbnRpbmVsLXNjaG9vbC1vZmZpY2lhbHMtc2hvb3RpbmdzLWZsYWctc29jaWFsLW1lZGlhKSwgeSBlbCBjw7NkaWdvIHBhcmEgcmVwbGljYXJsb3MgZXN0w6EgW2FxdcOtXShodHRwczovL2J1enpmZWVkbmV3cy5naXRodWIuaW8vMjAxOS0xMC1zb2NpYWwtc2VudGluZWwvKS4KCgpQb3Igw7psdGltbyAzIHJlcG9zIGRlIEdpdGh1YiBhc29jaWFkb3MgYWwgcHJveWVjdG8gW1RpZHl0dWVzZGF5XShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5KS4gQWJzb2x1dGFtZW50ZSBpbXByZXNpb25hbnRlcyEhIQoKCi0gPGh0dHBzOi8vZ2l0aHViLmNvbS9na2FyYW1hbmlzL3RpZHl0dWVzZGF5L3RyZWUvbWFzdGVyL3dlZWstMzg+CgotIDxodHRwczovL2dpdGh1Yi5jb20vc3ByZW45ZXIvdGlkeXR1ZXNkYXk+CgotIDxodHRwczovL2dpdGh1Yi5jb20vb3Rob21hbnRlZ2F6emEvY29kZS10aWR5dHVlc2RheT4KCgoKIyMgTWFzIGJpYmxpbwoKLSBbQSBHcmFtbWFyIG9mIEdyYXBoaWNzIGZvciBQeXRob25dKGh0dHBzOi8vcGxvdG5pbmUucmVhZHRoZWRvY3MuaW8vZW4vc3RhYmxlL2luZGV4Lmh0bWwpLiBgUGxvdG5pbmVgIGlzIGFuIGltcGxlbWVudGF0aW9uIG9mIGEgZ3JhbW1hciBvZiBncmFwaGljcyBpbiBQeXRob24sIGl0IGlzIGJhc2VkIG9uIGBnZ3Bsb3QyYC4gCgotIFtQeXRob24gUGxvdHRpbmcgZm9yIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXNdKGh0dHBzOi8vcHl0aG9ucGxvdC5jb20vKS4gVW4gcmVjb3JyaWRvIHBvciBsb3Mgc2lzdGVtYXMgZ3LDoWZpY29zIGRlIFBoeXRvbi4KCi0gW2dncGxvdDJ0b3JdKGh0dHBzOi8vZ2dwbG90MnR1dG9yLmNvbS8pLiBVbiBzaXRpbyB3ZWIgY29uIGJ1ZW5vcyB0dXRvcmlhbGVzIHNvYnJlIGVqZW1wbG9zIGRlIGdyw6FmaWNvcyBnZ3Bsb3QuIFZhbiBtZWpvcmFuZG8gZWwgZ3LDoWZpY28gcG9jbyBhIHBvY28uIFBvciBlamVtcGxvIFtlc3RlIHR1dG9yaWFsIHNvYnJlIHBvd2VybGlmdGluZ10oaHR0cHM6Ly9nZ3Bsb3QydHV0b3IuY29tL3Bvd2VybGlmdGluZy9zcXVhdHMvKQoKLSBVbiBbZWplbXBsbyBkZSBkb3RwbG90XShodHRwczovL2lrYXNobml0c2t5LmdpdGh1Yi5pby8yMDE5L2RvdHBsb3QvKSBJbHlhIEthc2huaXRza3kgZGUgIEFxdcOtIGVzdMOhIGVsIFtnaXN0XShodHRwczovL2dpc3QuZ2l0aHViLmNvbS9pa2FzaG5pdHNreS8yZjNlMmIyYWY2ZjUwOTExYmI3NzViYmNlNmViMGZiOCkuCgoKLSBbUiBHcmFwaGljcyBDb29rYm9va10oaHR0cHM6Ly9yLWdyYXBoaWNzLm9yZy8pCgotLS0tLS0tLS0tLS0tLS0tLS0KCmBgYHtyLCBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KIy0gbnVldmFzIGNvc2FzIGRlIGdncGxvdDIKcCA8LSBnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgUGV0YWwuTGVuZ3RoKSkgKyBnZW9tX2xpbmUoKQphYSA8LSBnZ3Bsb3RfYnVpbGQocCkgIy0gZnVuY2lvbiBudWV2YQoKIy0gZ2FyZmljb3MgdXBzZXQgeSB2ZW5uOiBodHRwczovL3d3dy5saXR0bGVtaXNzZGF0YS5jb20vYmxvZy9zZXQtYW5hbHlzaXMKIy0gaHR0cHM6Ly9kcW4ud2Vic2l0ZS9wb3N0L2ludGVyYWN0aXZlLW1la2tvLWNoYXJ0cy1pbi1yLwoKYGBgCgo=